[MERGE] mail/chatter complete review/refactoring
authorOlivier Dony <odo@openerp.com>
Thu, 6 Sep 2012 06:08:12 +0000 (08:08 +0200)
committerOlivier Dony <odo@openerp.com>
Thu, 6 Sep 2012 06:08:12 +0000 (08:08 +0200)
Attempt at cleaning up and simplifying the mail-related
APIs and code, including mail.thread, mail.message,
email.template, open-chatter and the related components.
This branch has a server part as well, taking care of
cleaning up the `needaction` mechanisms in base, as
well as a few related bits

bzr revid: odo@openerp.com-20120906060812-iiqj8h4yfl8rsx63

189 files changed:
addons/account/account_bank_statement.py
addons/account/account_invoice.py
addons/account/edi/invoice_action_data.xml
addons/account/i18n/account.pot
addons/account_accountant/account_accountant_data.xml
addons/account_followup/wizard/account_followup_print.py
addons/account_voucher/account_voucher.py
addons/account_voucher/account_voucher_data.xml
addons/analytic/analytic.py
addons/auth_reset_password/auth_reset_password.xml
addons/base_action_rule/base_action_rule.py
addons/base_calendar/base_calendar.py
addons/base_calendar/crm_meeting.py
addons/base_calendar/crm_meeting_view.xml
addons/base_status/base_stage.py
addons/base_status/base_state.py
addons/crm/crm_action_rule.py
addons/crm/crm_data.xml
addons/crm/crm_lead.py
addons/crm/crm_lead_demo.xml
addons/crm/crm_lead_view.xml
addons/crm/crm_meeting.py
addons/crm/crm_phonecall.py
addons/crm/crm_phonecall_view.xml
addons/crm/test/process/communication_with_customer.yml
addons/crm/test/process/lead2opportunity2win.yml
addons/crm/wizard/crm_lead_to_opportunity.py
addons/crm/wizard/crm_lead_to_opportunity_view.xml
addons/crm/wizard/crm_lead_to_partner.py
addons/crm/wizard/crm_phonecall_to_partner.py
addons/crm_claim/crm_claim.py
addons/crm_claim/test/ui/claim_demo.yml
addons/crm_helpdesk/crm_helpdesk.py
addons/crm_helpdesk/report/crm_helpdesk_report.py
addons/crm_helpdesk/test/process/help-desk.yml
addons/crm_partner_assign/crm_lead_view.xml
addons/crm_partner_assign/i18n/crm_partner_assign.pot
addons/crm_partner_assign/test/partner_assign.yml
addons/crm_partner_assign/wizard/crm_forward_to_partner.py
addons/crm_partner_assign/wizard/crm_forward_to_partner_view.xml
addons/email_template/email_template.py
addons/email_template/email_template_view.xml
addons/email_template/i18n/email_template.pot
addons/email_template/security/ir.model.access.csv
addons/email_template/tests/__init__.py [new file with mode: 0644]
addons/email_template/tests/test_mail.py [new file with mode: 0644]
addons/email_template/wizard/email_template_preview.py
addons/email_template/wizard/email_template_preview_view.xml
addons/email_template/wizard/mail_compose_message.py
addons/email_template/wizard/mail_compose_message_view.xml
addons/event/email_template.xml
addons/event/event.py
addons/event/event_data.xml
addons/event/event_view.xml
addons/event_sale/event_sale.py
addons/fetchmail/fetchmail.py
addons/fetchmail/fetchmail_view.xml
addons/hr/hr.py
addons/hr/hr_data.xml
addons/hr_evaluation/__openerp__.py
addons/hr_evaluation/hr_evaluation.py
addons/hr_evaluation/hr_evaluation_data.xml
addons/hr_evaluation/hr_evaluation_view.xml
addons/hr_evaluation/wizard/__init__.py [deleted file]
addons/hr_evaluation/wizard/hr_evaluation_mail.py [deleted file]
addons/hr_evaluation/wizard/hr_evaluation_mail_view.xml [deleted file]
addons/hr_evaluation/wizard/mail_compose_message.py [deleted file]
addons/hr_expense/__openerp__.py
addons/hr_expense/hr_expense_data.xml
addons/hr_holidays/__openerp__.py
addons/hr_holidays/hr_holidays.py
addons/hr_holidays/hr_holidays_data.xml
addons/hr_holidays/hr_holidays_view.xml
addons/hr_recruitment/hr_recruitment.py
addons/hr_recruitment/hr_recruitment_data.xml
addons/hr_recruitment/hr_recruitment_view.xml
addons/hr_timesheet_invoice/hr_timesheet_invoice.py
addons/hr_timesheet_sheet/__openerp__.py
addons/hr_timesheet_sheet/hr_timesheet_sheet_data.xml
addons/idea/idea.py
addons/import_base/import_framework.py
addons/import_sugarcrm/import_sugarcrm.py
addons/mail/__init__.py
addons/mail/__openerp__.py
addons/mail/data/mail_demo.xml
addons/mail/data/mail_group_data.xml
addons/mail/doc/mail_openchatter_howto.rst
addons/mail/doc/mail_state.rst
addons/mail/ir_needaction.py [deleted file]
addons/mail/mail_followers.py
addons/mail/mail_followers_view.xml
addons/mail/mail_group.py
addons/mail/mail_group_menu.py
addons/mail/mail_group_view.xml
addons/mail/mail_mail.py [new file with mode: 0644]
addons/mail/mail_mail_view.xml [new file with mode: 0644]
addons/mail/mail_message.py
addons/mail/mail_message_view.xml
addons/mail/mail_thread.py
addons/mail/res_partner.py
addons/mail/res_users.py
addons/mail/res_users_view.xml
addons/mail/static/src/css/mail.css
addons/mail/static/src/css/mail_compose_message.css
addons/mail/static/src/js/mail.js
addons/mail/static/src/js/mail_followers.js
addons/mail/static/src/xml/mail.xml
addons/mail/static/src/xml/mail_followers.xml
addons/mail/tests/test_mail.py
addons/mail/wizard/mail_compose_message.py
addons/mail/wizard/mail_compose_message_view.xml
addons/marketing_campaign/marketing_campaign_demo.xml
addons/marketing_campaign/test/marketing_campaign.yml
addons/marketing_campaign_crm_demo/marketing_campaign_demo.xml
addons/mrp/mrp.py
addons/mrp/mrp_data.xml
addons/mrp/mrp_demo.xml
addons/mrp/mrp_view.xml
addons/mrp/procurement.py
addons/mrp_operations/mrp_operations.py
addons/mrp_repair/mrp_repair.py
addons/plugin/plugin_handler.py
addons/point_of_sale/point_of_sale_data.xml
addons/portal/portal_demo.xml
addons/portal/wizard/portal_wizard.py
addons/procurement/procurement.py
addons/procurement/schedulers.py
addons/product/product.py
addons/project/project.py
addons/project/project_data.xml
addons/project/project_demo.xml
addons/project/project_view.xml
addons/project/security/project_security.xml
addons/project_gtd/project_gtd_data.xml
addons/project_issue/__openerp__.py
addons/project_issue/project_issue.py
addons/project_issue/project_issue_data.xml
addons/project_issue/project_issue_view.xml
addons/project_mailgate/__init__.py [deleted file]
addons/project_mailgate/__openerp__.py [deleted file]
addons/project_mailgate/i18n/ar.po [deleted file]
addons/project_mailgate/i18n/ca.po [deleted file]
addons/project_mailgate/i18n/cs.po [deleted file]
addons/project_mailgate/i18n/da.po [deleted file]
addons/project_mailgate/i18n/de.po [deleted file]
addons/project_mailgate/i18n/es.po [deleted file]
addons/project_mailgate/i18n/es_CR.po [deleted file]
addons/project_mailgate/i18n/es_MX.po [deleted file]
addons/project_mailgate/i18n/es_VE.po [deleted file]
addons/project_mailgate/i18n/fi.po [deleted file]
addons/project_mailgate/i18n/fr.po [deleted file]
addons/project_mailgate/i18n/gl.po [deleted file]
addons/project_mailgate/i18n/hr.po [deleted file]
addons/project_mailgate/i18n/hu.po [deleted file]
addons/project_mailgate/i18n/it.po [deleted file]
addons/project_mailgate/i18n/ja.po [deleted file]
addons/project_mailgate/i18n/lv.po [deleted file]
addons/project_mailgate/i18n/mn.po [deleted file]
addons/project_mailgate/i18n/nl.po [deleted file]
addons/project_mailgate/i18n/nl_BE.po [deleted file]
addons/project_mailgate/i18n/pl.po [deleted file]
addons/project_mailgate/i18n/project_mailgate.pot [deleted file]
addons/project_mailgate/i18n/pt.po [deleted file]
addons/project_mailgate/i18n/pt_BR.po [deleted file]
addons/project_mailgate/i18n/ro.po [deleted file]
addons/project_mailgate/i18n/sv.po [deleted file]
addons/project_mailgate/i18n/tr.po [deleted file]
addons/project_mailgate/i18n/zh_CN.po [deleted file]
addons/project_mailgate/project_mailgate.py [deleted file]
addons/purchase/edi/purchase_order_action_data.xml
addons/purchase/i18n/purchase.pot
addons/purchase/purchase.py
addons/purchase/purchase_data.xml
addons/purchase/purchase_view.xml
addons/purchase_requisition/purchase_requisition.py
addons/purchase_requisition/purchase_requisition_view.xml
addons/sale/edi/sale_order_action_data.xml
addons/sale/i18n/sale.pot
addons/sale/sale.py
addons/sale/sale_data.xml
addons/sale/sale_demo.xml
addons/sale/sale_view.xml
addons/sale_crm/wizard/crm_make_sale.py
addons/share/wizard/share_wizard.py
addons/stock/stock.py
addons/stock/stock_data.xml
addons/stock/wizard/stock_change_product_qty.py
addons/survey/wizard/survey_answer.py
addons/survey/wizard/survey_send_invitation.py

index 8df291a..095d461 100644 (file)
@@ -430,7 +430,7 @@ class account_bank_statement(osv.osv):
                     'name': st_number,
                     'balance_end_real': st.balance_end
             }, context=context)
-            self.message_append_note(cr, uid, [st.id], body=_('Statement %s is confirmed, journal items are created.') % (st_number,), context=context)
+            self.message_post(cr, uid, [st.id], body=_('Statement %s confirmed, journal items were created.') % (st_number,), context=context)
         return self.write(cr, uid, ids, {'state':'confirm'}, context=context)
 
     def button_cancel(self, cr, uid, ids, context=None):
index 78ef090..15027b7 100644 (file)
@@ -1045,7 +1045,7 @@ class account_invoice(osv.osv):
                 if obj_inv.type in ('out_invoice', 'out_refund'):
                     ctx = self.get_log_context(cr, uid, context=ctx)
                 message = _("Invoice  '%s' is validated.") % name
-                self.message_append_note(cr, uid, [inv_id], body=message, context=context)
+                self.message_post(cr, uid, [inv_id], body=message, context=context)
         return True
 
     def action_cancel(self, cr, uid, ids, *args):
@@ -1275,7 +1275,7 @@ class account_invoice(osv.osv):
             # TODO: use currency's formatting function
             msg = _("Invoice '%s' is paid partially: %s%s of %s%s (%s%s remaining).") % \
                     (name, pay_amount, code, invoice.amount_total, code, total, code)
-            self.message_append_note(cr, uid, [inv_id], body=msg, context=context)
+            self.message_post(cr, uid, [inv_id], body=msg, context=context)
             self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
 
         # Update the stored value (fields.function), so we write to trigger recompute
@@ -1288,24 +1288,25 @@ class account_invoice(osv.osv):
 
     def _get_document_type(self, type):
         type_dict = {
-                'out_invoice': 'Customer invoice',
-                'in_invoice': 'Supplier invoice',
-                'out_refund': 'Customer Refund',
-                'in_refund': 'Supplier Refund',
+                # Translation markers will have no effect at runtime, only used to properly flag export
+                'out_invoice': _('Customer invoice'),
+                'in_invoice': _('Supplier invoice'),
+                'out_refund': _('Customer Refund'),
+                'in_refund': _('Supplier Refund'),
         }
         return type_dict.get(type, 'Invoice')
 
     def create_send_note(self, cr, uid, ids, context=None):
         for obj in self.browse(cr, uid, ids, context=context):
-            self.message_append_note(cr, uid, [obj.id],body=_("%s <b>created</b>.") % (self._get_document_type(obj.type)), context=context)
+            self.message_post(cr, uid, [obj.id], body=_("%s <b>created</b>.") % (_(self._get_document_type(obj.type))), context=context)
 
     def confirm_paid_send_note(self, cr, uid, ids, context=None):
-         for obj in self.browse(cr, uid, ids, context=context):
-            self.message_append_note(cr, uid, [obj.id], body=_("%s <b>paid</b>.") % (self._get_document_type(obj.type)), context=context)
+        for obj in self.browse(cr, uid, ids, context=context):
+            self.message_post(cr, uid, [obj.id], body=_("%s <b>paid</b>.") % (_(self._get_document_type(obj.type))), context=context)
 
     def invoice_cancel_send_note(self, cr, uid, ids, context=None):
         for obj in self.browse(cr, uid, ids, context=context):
-            self.message_append_note(cr, uid, [obj.id], body=_("%s <b>cancelled</b>.") % (self._get_document_type(obj.type)), context=context)
+            self.message_post(cr, uid, [obj.id], body=_("%s <b>cancelled</b>.") % (_(self._get_document_type(obj.type))), context=context)
 
 account_invoice()
 
index 0eec6d2..53fa03a 100644 (file)
     </div>
 </div>
             ]]></field>
-            <field name="body_text"><![CDATA[
-Hello${object.partner_id.name and ' ' or ''}${object.partner_id.name or ''},
-
-A new invoice is available for ${object.partner_id.name}:
-       | Invoice number: *${object.number}*
-       | Invoice total: *${object.amount_total} ${object.currency_id.name}*
-       | Invoice date: ${object.date_invoice}
-       % if object.origin:
-       | Order reference: ${object.origin}
-       % endif
-       | Your contact: ${object.user_id.name} ${object.user_id.email and '<%s>'%(object.user_id.email) or ''}
-
-You can view the invoice document, download it and pay online using the following link:
-    ${ctx.get('edi_web_url_view') or 'n/a'}
-
-% if object.company_id.paypal_account and object.type in ('out_invoice', 'in_refund'):
-<% 
-comp_name = quote(object.company_id.name)
-inv_number = quote(object.number)
-paypal_account = quote(object.company_id.paypal_account)
-inv_amount = quote(str(object.amount_total))
-cur_name = quote(object.currency_id.name)
-paypal_url = "https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=%s&item_name=%s%%20Invoice%%20%s"\
-             "&invoice=%s&amount=%s&currency_code=%s&button_subtype=services&no_note=1&bn=OpenERP_Invoice_PayNow_%s" % \
-             (paypal_account,comp_name,inv_number,inv_number,inv_amount,cur_name,cur_name)
-%>
-It is also possible to directly pay with Paypal:
-    ${paypal_url}
-% endif
-
-If you have any question, do not hesitate to contact us.
-
-
-Thank you for choosing ${object.company_id.name}!
-
-
---
-${object.user_id.name} ${object.user_id.email and '<%s>'%(object.user_id.email) or ''}
-${object.company_id.name}
-% if object.company_id.street:
-${object.company_id.street or ''}
-% endif
-% if object.company_id.street2:
-${object.company_id.street2}
-% endif
-% if object.company_id.city or object.company_id.zip:
-${object.company_id.zip or ''} ${object.company_id.city or ''}
-% endif
-% if object.company_id.country_id:
-${object.company_id.state_id and ('%s, ' % object.company_id.state_id.name) or ''} ${object.company_id.country_id.name or ''}
-% endif
-% if object.company_id.phone:
-Phone: ${object.company_id.phone}
-% endif
-% if object.company_id.website:
-${object.company_id.website or ''}
-% endif
-            ]]></field>
         </record>
     </data>
 </openerp>
index 174002a..0d54397 100644 (file)
@@ -9538,7 +9538,7 @@ msgid "Refund"
 msgstr ""
 
 #. module: account
-#: model:email.template,body_text:account.email_template_edi_invoice
+#: model:email.template,body:account.email_template_edi_invoice
 msgid "\n"
 "Hello${object.address_invoice_id.name and ' ' or ''}${object.address_invoice_id.name or ''},\n"
 "\n"
index 383b53c..5b56301 100644 (file)
         </record>
 
         <!-- Notify all employees of module installation -->
-        <function model="mail.group" name="message_append_note">
-            <!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
-            <value eval="[ref('mail.group_all_employees')]"/>
-            <value>Module Accounting and Finance has been installed.</value>
-            <value>With OpenERP's accounting, you can get an instant access to all your financial data, setup your analytic accounting, forecast your taxes, control your budgets, easily create and send invoices, record bank statements, etc.
+        <record model="mail.message" id="module_install_notification">
+            <field name="model">mail.group</field>
+            <field name="res_id" ref="mail.group_all_employees"/>
+            <field name="type">notification</field>
+            <field name="subject">Accounting and Finance application installed!</field>
+            <field name="body">With OpenERP's accounting, you get instant access to your financial data, and can setup analytic accounting, forecast taxes, control budgets, easily create and send invoices, record bank statements, etc.
 
-The accounting features are fully integrated with others OpenERP applications to automate all your processes: creation of customer invoices, control of supplier invoices, point-of-sale integration, automated follow-ups, etc.</value>
-        </function>
+The accounting features are fully integrated with other OpenERP applications to automate all your processes: creation of customer invoices, control of supplier invoices, point-of-sale integration, automated follow-ups, etc.</field>
+        </record>
     </data>
 </openerp>
index d151d21..1e84681 100644 (file)
@@ -213,8 +213,6 @@ class account_followup_print_all(osv.osv_memory):
         mod_obj = self.pool.get('ir.model.data')
         move_obj = self.pool.get('account.move.line')
         user_obj = self.pool.get('res.users')
-        line_obj = self.pool.get('account_followup.stat')
-        mail_message = self.pool.get('mail.message')
 
         if context is None:
             context = {}
@@ -235,13 +233,7 @@ class account_followup_print_all(osv.osv_memory):
                     total_amt += line.debit - line.credit
                 dest = False
                 if partner:
-                    if partner.type=='contact':
-                        if adr.email:
-                            dest = [partner.email]
-                    if (not dest) and partner.type=='default':
-                        if partner.email:
-                            dest = [partner.email]
-                src = tools.config.options['email_from']
+                    dest = [partner.email]
                 if not data.partner_lang:
                     body = data.email_body
                 else:
@@ -281,7 +273,12 @@ class account_followup_print_all(osv.osv_memory):
                 msg = ''
                 if dest:
                     try:
-                        mail_message.schedule_with_attach(cr, uid, src, dest, sub, body, context=context)
+                        vals = {'state': 'outgoing',
+                                'subject': sub,
+                                'body_html': '<pre>%s</pre>' % body,
+                                'email_to': dest,
+                                'email_from': data_user.email or tools.config.options['email_from']}
+                        self.pool.get('mail.mail').create(cr, uid, vals, context=context)
                         msg_sent += partner.name + '\n'
                     except Exception, e:
                         raise osv.except_osv('Error !', e )
index 223705a..f75cda8 100644 (file)
@@ -1293,17 +1293,17 @@ class account_voucher(osv.osv):
     def create_send_note(self, cr, uid, ids, context=None):
         for obj in self.browse(cr, uid, ids, context=context):
             message = "%s <b>created</b>." % self._document_type[obj.type or False]
-            self.message_append_note(cr, uid, [obj.id], body=message, context=context)
+            self.message_post(cr, uid, [obj.id], body=message, context=context)
 
     def post_send_note(self, cr, uid, ids, context=None):
         for obj in self.browse(cr, uid, ids, context=context):
             message = "%s '%s' is <b>posted</b>." % (self._document_type[obj.type or False], obj.move_id.name)
-            self.message_append_note(cr, uid, [obj.id], body=message, context=context)
+            self.message_post(cr, uid, [obj.id], body=message, context=context)
 
     def reconcile_send_note(self, cr, uid, ids, context=None):
         for obj in self.browse(cr, uid, ids, context=context):
             message = "%s <b>reconciled</b>." % self._document_type[obj.type or False]
-            self.message_append_note(cr, uid, [obj.id], body=message, context=context)
+            self.message_post(cr, uid, [obj.id], body=message, context=context)
 
 account_voucher()
 
index 8be2851..656d2c2 100644 (file)
@@ -2,15 +2,16 @@
 <openerp>
     <data noupdate="1">
         <!-- notify all employees of module installation -->
-        <function model="mail.group" name="message_append_note">
-            <!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
-            <value eval="[ref('mail.group_all_employees')]"/>
-            <value>Module eInvoicing &amp; Payments has been installed.</value>
-            <value>OpenERP's electronic invoicing allows to ease and fasten the creation of invoices and collection of customer payments. Invoices are created in a few clicks and your customers receive them by email. They can pay online and/or import them in their own system.
+        <record model="mail.message" id="module_install_notification">
+            <field name="model">mail.group</field>
+            <field name="res_id" ref="mail.group_all_employees"/>
+            <field name="type">notification</field>
+            <field name="subject">eInvoicing &amp; Payments application installed!</field>
+            <field name="body">OpenERP's electronic invoicing accelerates the creation of invoices and collection of customer payments. Invoices are created in a few clicks and your customers receive them by email. They can pay online and/or import them in their own system.
 
-You can track customer payments easily and automate the reminders. You get an overview of the discussion with your customers on each invoice to ensure a full traceability.
+You can track customer payments easily and automate follow-ups. You get an overview of the discussion with your customers on each invoice for easier traceability.
 
-If you want to use advanced accounting features, you should install the "Accounting and Finance" module.</value>
-        </function>
+For advanced accounting features, you should install the "Accounting and Finance" module.</field>
+        </record>
     </data>
 </openerp>
index 7eeb926..d90e200 100644 (file)
@@ -297,7 +297,7 @@ class account_analytic_account(osv.osv):
 
     def create_send_note(self, cr, uid, ids, context=None):
         for obj in self.browse(cr, uid, ids, context=context):
-            self.message_append_note(cr, uid, [obj.id], body=_("Contract for <em>%s</em> has been <b>created</b>.") % (obj.partner_id.name), context=context)
+            self.message_post(cr, uid, [obj.id], body=_("Contract for <em>%s</em> has been <b>created</b>.") % (obj.partner_id.name), context=context)
 
 account_analytic_account()
 
index 26ea887..b7d7e0b 100644 (file)
@@ -9,16 +9,13 @@
             <field name="email_from"><![CDATA[${object.company_id.name} <${object.company_id.email}>]]></field>
             <field name="email_to" eval="False"><!--(set by reset_password module)--></field>
             <field name="subject">Password reset</field>
-            <field name="body_text"><![CDATA[
-A password reset was requested the OpenERP account linked to this email on ${object._auth_reset_password_host()}
+            <field name="body_html"><![CDATA[
+<p>A password reset was requested the OpenERP account linked to this email on ${object._auth_reset_password_host()}</p>
 
-You may change your password following this link:
+<p>You may change your password following this <a href="${object._auth_reset_password_link()}">link</a>,
+or by copy-pasting the following URL in your browser: ${object._auth_reset_password_link()}</p>
 
-${object._auth_reset_password_link()}
-
-If you don't have asked for password reset, you can safely ignore this email.
-
-]]></field>
+<p>Note: If you did not ask for a password reset, you can safely ignore this email.</p>]]></field>
         </record>
 
         <!-- TODO get own css -->
index b6eacda..7e9bbac 100644 (file)
@@ -302,33 +302,27 @@ the rule to mark CC(mail to any other person defined in actions)."),
         return self.format_body(body % data)
 
     def email_send(self, cr, uid, obj, emails, body, emailfrom=None, context=None):
-        """ send email
-            @param self: The object pointer
-            @param cr: the current row, from the database cursor,
-            @param uid: the current user’s ID for security checks,
-            @param email: pass the emails
-            @param emailfrom: Pass name the email From else False
-            @param context: A standard dictionary for contextual values """
-
         if not emailfrom:
-            emailfrom = tools.config.get('email_from', False)
-
-        if context is None:
-            context = {}
-
-        mail_message = self.pool.get('mail.message')
+            emailfrom = tools.config.get('email_from')
         body = self.format_mail(obj, body)
-        if not emailfrom:
-            if hasattr(obj, 'user_id') and obj.user_id and obj.user_id.email:
-                emailfrom = obj.user_id.email
-
-        name = '[%d] %s' % (obj.id, tools.ustr(obj.name))
+        if not emailfrom and hasattr(obj, 'user_id') and obj.user_id and obj.user_id.email:
+            emailfrom = obj.user_id.email
         emailfrom = tools.ustr(emailfrom)
         reply_to = emailfrom
         if not emailfrom:
             raise osv.except_osv(_('Error!'),
-                    _("No email ID found for your company address."))
-        return mail_message.schedule_with_attach(cr, uid, emailfrom, emails, name, body, model='base.action.rule', reply_to=reply_to, res_id=obj.id)
+                                 _("Missing default email address or missing email on responsible user"))
+        return self.pool.get('mail.mail').create(cr, uid,
+                {   'email_from': emailfrom,
+                    'email_to': emails.join(','),
+                    'reply_to': reply_to,
+                    'state': 'outgoing',
+                    'subject': '[%d] %s' % (obj.id, tools.ustr(obj.name)),
+                    'body_html': '<pre>%s</pre>' % body,
+                    'res_id': obj.id,
+                    'model': obj._table_name,
+                    'auto_delete': True
+                }, context=context)
 
 
     def do_check(self, cr, uid, action, obj, context=None):
@@ -438,11 +432,8 @@ the rule to mark CC(mail to any other person defined in actions)."),
         if len(emails) and action.act_mail_body:
             emails = list(set(emails))
             email_from = safe_eval(action.act_email_from, {}, locals_for_emails)
-
-            def to_email(text):
-                return re.findall(r'([^ ,<@]+@[^> ,]+)', text or '')
-            emails = to_email(','.join(filter(None, emails)))
-            email_froms = to_email(email_from)
+            emails = tools.email_split(','.join(filter(None, emails)))
+            email_froms = tools.email_split(email_from)
             if email_froms:
                 self.email_send(cr, uid, obj, emails, action.act_mail_body, emailfrom=email_froms[0])
         return True
index be3244f..a6d5f68 100644 (file)
@@ -471,18 +471,10 @@ property or property parameter."),
     def _send_mail(self, cr, uid, ids, mail_to, email_from=tools.config.get('email_from', False), context=None):
         """
         Send mail for event invitation to event attendees.
-        @param cr: the current row, from the database cursor,
-        @param uid: the current user’s ID for security checks,
-        @param ids: List of attendee’s IDs.
         @param email_from: Email address for user sending the mail
-        @param context: A standard dictionary for contextual values
         @return: True
         """
-        if context is None:
-            context = {}
-
         company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.name
-        mail_message = self.pool.get('mail.message')
         for att in self.browse(cr, uid, ids, context=context):
             sign = att.sent_by_uid and att.sent_by_uid.signature or ''
             sign = '<br>'.join(sign and sign.split('\n') or [])
@@ -508,17 +500,18 @@ property or property parameter."),
                 }
                 body = html_invitation % body_vals
                 if mail_to and email_from:
-                    attach = self.get_ics_file(cr, uid, res_obj, context=context)
-                    mail_message.schedule_with_attach(cr, uid,
-                        email_from,
-                        mail_to,
-                        sub,
-                        body,
-                        attachments=attach and {'invitation.ics': attach} or None,
-                        content_subtype='html',
-                        reply_to=email_from,
-                        context=context
-                    )
+                    ics_file = self.get_ics_file(cr, uid, res_obj, context=context)
+                    vals = {'email_from': email_from,
+                            'email_to': mail_to,
+                            'state': 'outgoing',
+                            'subject': sub,
+                            'body_html': body,
+                            'auto_delete': True}
+                    if ics_file:
+                        vals['attachment_ids'] = [(0,0,{'name': 'invitation.ics',
+                                                        'datas_fname': 'invitation.ics',
+                                                        'datas': str(ics_file).encode('base64')})]
+                    self.pool.get('mail.mail').create(cr, uid, vals, context=context)
             return True
 
     def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv):
@@ -812,7 +805,6 @@ class calendar_alarm(osv.osv):
         """
         if context is None:
             context = {}
-        mail_message = self.pool.get('mail.message')
         current_datetime = datetime.now()
         alarm_ids = self.search(cr, uid, [('state', '!=', 'done')], context=context)
 
@@ -849,36 +841,10 @@ class calendar_alarm(osv.osv):
             else:
                 re_dates = [alarm.trigger_date]
 
-            for r_date in re_dates:
-                ref = alarm.model_id.model + ',' + str(alarm.res_id)
-
-                # search for alreay sent requests
-                #if request_obj.search(cr, uid, [('trigger_date', '=', r_date), ('ref_doc1', '=', ref)], context=context):
-                    #continue
-
-                # Deactivated because of the removing of res.request
-                # TODO: when cleaning calendar module, re-add this in a new mechanism
-                #if alarm.action == 'display':
-                    #value = {
-                       #'name': alarm.name,
-                       #'act_from': alarm.user_id.id,
-                       #'act_to': alarm.user_id.id,
-                       #'body': alarm.description,
-                       #'trigger_date': r_date,
-                       #'ref_doc1': ref
-                    #}
-                    #request_id = request_obj.create(cr, uid, value)
-                    #request_ids = [request_id]
-                    #for attendee in res_obj.attendee_ids:
-                        #if attendee.user_id:
-                            #value['act_to'] = attendee.user_id.id
-                            #request_id = request_obj.create(cr, uid, value)
-                            #request_ids.append(request_id)
-                    #request_obj.request_send(cr, uid, request_ids)
-
+            if re_dates:
                 if alarm.action == 'email':
-                    sub = '[Openobject Reminder] %s' % (alarm.name)
-                    body = """
+                    sub = '[OpenERP Reminder] %s' % (alarm.name)
+                    body = """<pre>
 Event: %s
 Event Date: %s
 Description: %s
@@ -888,20 +854,21 @@ From:
 
 ----
 %s
-
+</pre>
 """  % (alarm.name, alarm.trigger_date, alarm.description, \
                         alarm.user_id.name, alarm.user_id.signature)
                     mail_to = [alarm.user_id.email]
                     for att in alarm.attendee_ids:
                         mail_to.append(att.user_id.email)
                     if mail_to:
-                        mail_message.schedule_with_attach(cr, uid,
-                            tools.config.get('email_from', False),
-                            mail_to,
-                            sub,
-                            body,
-                            context=context
-                        )
+                        vals = {
+                            'state': 'outgoing',
+                            'subject': sub,
+                            'body_html': body,
+                            'email_to': mail_to,
+                            'email_from': tools.config.get('email_from', mail_to),
+                        }
+                        self.pool.get('mail.mail').create(cr, uid, vals, context=context)
             if next_trigger_date:
                 update_vals.update({'trigger_date': next_trigger_date})
             else:
@@ -1616,36 +1583,6 @@ class calendar_todo(osv.osv):
 
 calendar_todo()
 
-class ir_attachment(osv.osv):
-    _name = 'ir.attachment'
-    _inherit = 'ir.attachment'
-
-    def search_count(self, cr, user, args, context=None):
-        new_args = []
-        for domain_item in args:
-            if isinstance(domain_item, (list, tuple)) and len(domain_item) == 3 and domain_item[0] == 'res_id':
-                new_args.append((domain_item[0], domain_item[1], base_calendar_id2real_id(domain_item[2])))
-            else:
-                new_args.append(domain_item)
-        return super(ir_attachment, self).search_count(cr, user, new_args, context)
-
-    def create(self, cr, uid, vals, context=None):
-        if context:
-            id = context.get('default_res_id', False)
-            context.update({'default_res_id' : base_calendar_id2real_id(id)})
-        return super(ir_attachment, self).create(cr, uid, vals, context=context)
-
-    def search(self, cr, uid, args, offset=0, limit=None, order=None,
-            context=None, count=False):
-        new_args = []
-        for domain_item in args:
-            if isinstance(domain_item, (list, tuple)) and len(domain_item) == 3 and domain_item[0] == 'res_id':
-                new_args.append((domain_item[0], domain_item[1], base_calendar_id2real_id(domain_item[2])))
-            else:
-                new_args.append(domain_item)
-        return super(ir_attachment, self).search(cr, uid, new_args, offset=offset,
-                            limit=limit, order=order, context=context, count=False)
-ir_attachment()
 
 class ir_values(osv.osv):
     _inherit = 'ir.values'
index cb96ff2..e1c6614 100644 (file)
@@ -43,7 +43,7 @@ class crm_meeting(base_state, osv.Model):
     _name = 'crm.meeting'
     _description = "Meeting"
     _order = "id desc"
-    _inherit = ["calendar.event", 'ir.needaction_mixin', "mail.thread"]
+    _inherit = ["calendar.event", "mail.thread", 'ir.needaction_mixin']
     _columns = {
         # base_state required fields
         'create_date': fields.datetime('Creation Date', readonly=True),
@@ -70,13 +70,17 @@ class crm_meeting(base_state, osv.Model):
     # OpenChatter
     # ----------------------------------------
 
+    # shows events of the day for this user
+    def needaction_domain_get(self, cr, uid, domain=[], context={}):
+        return [('date','<=',time.strftime('%Y-%M-%D 23:59:59')), ('date_deadline','>=', time.strftime('%Y-%M-%D 00:00:00')), ('user_id','=',uid)]
+
     def case_get_note_msg_prefix(self, cr, uid, id, context=None):
         return 'Meeting'
 
     def case_open_send_note(self, cr, uid, ids, context=None):
-        return self.message_append_note(cr, uid, ids, body=_("Meeting has been <b>confirmed</b>."), context=context)
+        return self.message_post(cr, uid, ids, body=_("Meeting <b>confirmed</b>."), context=context)
 
     def case_close_send_note(self, cr, uid, ids, context=None):
-        return self.message_append_note(cr, uid, ids, body=_("Meeting has been <b>done</b>."), context=context)
+        return self.message_post(cr, uid, ids, body=_("Meeting <b>completed</b>."), context=context)
 
 
index 6009251..928f8d9 100644 (file)
                     <page string="Invitation Detail">
                         <button string="Invite People"
                             name="%(base_calendar.action_view_calendar_invite_attendee_wizard)d"
-                            icon="terp-partner" type="action"
+                            type="action"
+                            attrs="{'readonly': [('state', '=', 'done')]}"
                             context="{'model' : 'crm.meeting', 'attendee_field':'attendee_ids'}" colspan="2"/>
                         <field name="attendee_ids" widget="one2many" mode="tree">
                             <tree string="Invitation details" editable="top">
                                 <button name="do_tentative"
                                     states="needs-action,declined,accepted"
                                     string="Uncertain" type="object"
-                                    icon="terp-crm" />
+                                    />
                                 <button name="do_accept" string="Accept"
                                     states="needs-action,tentative,declined"
-                                    type="object" icon="gtk-apply" />
+                                    type="object" />
                                 <button name="do_decline" string="Decline"
                                     states="needs-action,tentative,accepted"
-                                    type="object" icon="gtk-cancel" />
+                                    type="object" />
                                 <button
                                     name="%(base_calendar.action_view_calendar_invite_attendee_wizard)d"
                                     string="Delegate" type="action"
-                                    icon="gtk-sort-descending"
                                     states="needs-action,tentative,declined,accepted"
                                     context="{'model' : 'calendar.attendee', 'attendee_field' : 'child_ids'}" />
                             </tree>
                                 <header>
                                     <button name="do_tentative" type="object"
                                         states="needs-action,declined,accepted"
-                                        string="Uncertain" icon="terp-crm" />
+                                        string="Uncertain" />
                                     <button name="do_accept" type="object"
                                         states="needs-action,tentative,declined"
-                                        string="Accept" icon="gtk-apply" />
+                                        string="Accept" />
                                     <button name="do_decline" type="object"
                                         states="needs-action,tentative,accepted"
-                                        string="Decline" icon="gtk-cancel" />
+                                        string="Decline" />
                                     <button name="%(base_calendar.action_view_calendar_invite_attendee_wizard)d" type="action"
                                         states="needs-action,tentative,declined,accepted"
-                                        string="Delegate" icon="gtk-sort-descending"
+                                        string="Delegate" 
                                         context="{'model' : 'calendar.attendee', 'attendee_field' : 'child_ids'}" />
                                     <field name="state" widget="statusbar" statusbar_visible="draft,open,done"/>
                                 </header>
         <field name="name">CRM - Meetings Tree</field>
         <field name="model">crm.meeting</field>
         <field name="arch" type="xml">
-            <tree string="Meetings" fonts="bold:needaction_pending==True">
+            <tree string="Meetings" fonts="bold:message_unread==True">
                 <field name="name" string="Subject" />
                 <field name="user_id"/>
                 <field name="date"/>
                 <field name="state" invisible="True"/>
                 <field name="duration" />
-                <field name="needaction_pending" invisible="1"/>
+                <field name="message_unread" invisible="1"/>
             </tree>
         </field>
     </record>
         <field name="arch" type="xml">
             <search string="Search Meetings">
                 <field name="name" string="Meeting" filter_domain="[('name','ilike',self)]"/>
-                <filter string="Inbox" help="Unread messages" icon="terp-mail-message-new" name="needaction_pending" domain="[('needaction_pending','=',True)]"/>
+                <filter string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/>
                 <separator/>
-                <filter string="My Meetings" help="My Meetings" icon="terp-personal" domain="[('user_id','=',uid)]"/>
+                <filter string="My Meetings" help="My Meetings" domain="[('user_id','=',uid)]"/>
                 <field name="user_id"/>
                 <field name="partner_ids"/>
             </search>
index 775de51..529e70f 100644 (file)
@@ -297,55 +297,31 @@ class base_stage(object):
                 destination=False)
 
     def remind_user(self, cr, uid, ids, context=None, attach=False, destination=True):
-        mail_message = self.pool.get('mail.message')
-        for case in self.browse(cr, uid, ids, context=context):
-            if not destination and not case.email_from:
-                return False
-            if not case.user_id.email:
-                return False
-            if destination and case.section_id.user_id:
-                case_email = case.section_id.user_id.email
-            else:
-                case_email = case.user_id.email
-
-            src = case_email
-            dest = case.user_id.email or ""
-            body = case.description or ""
-            for message in case.message_ids:
-                if message.email_from and message.body_text:
-                    body = message.body_text
-                    break
-
-            if not destination:
-                src, dest = dest, case.email_from
-                if body and case.user_id.signature:
-                    if body:
-                        body += '\n\n%s' % (case.user_id.signature)
-                    else:
-                        body = '\n\n%s' % (case.user_id.signature)
-
-            body = self.format_body(body)
-
-            attach_to_send = {}
-
-            if attach:
-                attach_ids = self.pool.get('ir.attachment').search(cr, uid, [('res_model', '=', self._name), ('res_id', '=', case.id)])
-                attach_to_send = self.pool.get('ir.attachment').read(cr, uid, attach_ids, ['datas_fname', 'datas'])
-                attach_to_send = dict(map(lambda x: (x['datas_fname'], base64.decodestring(x['datas'])), attach_to_send))
-
-            # Send an email
-            subject = "Reminder: [%s] %s" % (str(case.id), case.name, )
-            mail_message.schedule_with_attach(cr, uid,
-                src,
-                [dest],
-                subject,
-                body,
-                model=self._name,
-                reply_to=case.section_id.reply_to,
-                res_id=case.id,
-                attachments=attach_to_send,
-                context=context
-            )
+        if 'message_post' in self:
+            for case in self.browse(cr, uid, ids, context=context):
+                if destination:
+                    recipient_id = case.user_id.partner_id.id
+                else:
+                    if not case.email_from:
+                        return False
+                    recipient_id = self.pool.get('res.partner').find_or_create(cr, uid, case.email_from, context=context)
+                
+                body = case.description or ""
+                for message in case.message_ids:
+                    if message.type == 'email' and message.body:
+                        body = message.body
+                        break
+                body = self.format_body(body)
+                attach_to_send = {}
+                if attach:
+                    attach_ids = self.pool.get('ir.attachment').search(cr, uid, [('res_model', '=', self._name), ('res_id', '=', case.id)])
+                    attach_to_send = self.pool.get('ir.attachment').read(cr, uid, attach_ids, ['datas_fname', 'datas'])
+                    attach_to_send = dict(map(lambda x: (x['datas_fname'], x['datas'].decode('base64')), attach_to_send))
+                subject = "Reminder: [%s] %s" % (case.id, case.name)
+                self.message_post(cr, uid, case.id, body=body,
+                    subject=subject, attachments=attach_to_send, 
+                    partner_ids=[recipient_id], context=context)
         return True
 
     def _check(self, cr, uid, ids=False, context=None):
@@ -360,17 +336,6 @@ class base_stage(object):
     def format_mail(self, obj, body):
         return self.pool.get('base.action.rule').format_mail(obj, body)
 
-    def message_thread_followers(self, cr, uid, ids, context=None):
-        res = {}
-        for case in self.browse(cr, uid, ids, context=context):
-            l=[]
-            if case.email_cc:
-                l.append(case.email_cc)
-            if case.user_id and case.user_id.email:
-                l.append(case.user_id.email)
-            res[case.id] = l
-        return res
-
     # ******************************
     # Notifications
     # ******************************
@@ -395,31 +360,31 @@ class base_stage(object):
     def case_open_send_note(self, cr, uid, ids, context=None):
         for id in ids:
             msg = _('%s has been <b>opened</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
-            self.message_append_note(cr, uid, [id], body=msg, context=context)
+            self.message_post(cr, uid, [id], body=msg, context=context)
         return True
 
     def case_close_send_note(self, cr, uid, ids, context=None):
         for id in ids:
             msg = _('%s has been <b>closed</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
-            self.message_append_note(cr, uid, [id], body=msg, context=context)
+            self.message_post(cr, uid, [id], body=msg, context=context)
         return True
 
     def case_cancel_send_note(self, cr, uid, ids, context=None):
         for id in ids:
             msg = _('%s has been <b>canceled</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
-            self.message_append_note(cr, uid, [id], body=msg, context=context)
+            self.message_post(cr, uid, [id], body=msg, context=context)
         return True
 
     def case_pending_send_note(self, cr, uid, ids, context=None):
         for id in ids:
             msg = _('%s is now <b>pending</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
-            self.message_append_note(cr, uid, [id], body=msg, context=context)
+            self.message_post(cr, uid, [id], body=msg, context=context)
         return True
 
     def case_reset_send_note(self, cr, uid, ids, context=None):
         for id in ids:
             msg = _('%s has been <b>renewed</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
-            self.message_append_note(cr, uid, [id], body=msg, context=context)
+            self.message_post(cr, uid, [id], body=msg, context=context)
         return True
 
     def case_escalate_send_note(self, cr, uid, ids, new_section=None, context=None):
@@ -428,5 +393,5 @@ class base_stage(object):
                 msg = '%s has been <b>escalated</b> to <b>%s</b>.' % (self.case_get_note_msg_prefix(cr, uid, id, context=context), new_section.name)
             else:
                 msg = '%s has been <b>escalated</b>.' % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
-            self.message_append_note(cr, uid, [id], 'System Notification', msg, context=context)
+            self.message_post(cr, uid, [id], body=msg, context=context)
         return True
index 8fa2f92..cebf553 100644 (file)
@@ -179,13 +179,13 @@ class base_state(object):
     # Notifications
     # ******************************
     
-       def case_get_note_msg_prefix(self, cr, uid, id, context=None):
-               return ''
-       
+    def case_get_note_msg_prefix(self, cr, uid, id, context=None):
+        return ''
+
     def case_open_send_note(self, cr, uid, ids, context=None):
         for id in ids:
             msg = _('%s has been <b>opened</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
-            self.message_append_note(cr, uid, [id], body=msg, context=context)
+            self.message_post(cr, uid, [id], body=msg, context=context)
         return True
 
     def case_escalate_send_note(self, cr, uid, ids, new_section=None, context=None):
@@ -194,29 +194,29 @@ class base_state(object):
                 msg = '%s has been <b>escalated</b> to <b>%s</b>.' % (self.case_get_note_msg_prefix(cr, uid, id, context=context), new_section.name)
             else:
                 msg = '%s has been <b>escalated</b>.' % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
-            self.message_append_note(cr, uid, [id], 'System Notification', msg, context=context)
+            self.message_post(cr, uid, [id], body=msg, context=context)
         return True
 
     def case_close_send_note(self, cr, uid, ids, context=None):
         for id in ids:
             msg = _('%s has been <b>closed</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
-            self.message_append_note(cr, uid, [id], body=msg, context=context)
+            self.message_post(cr, uid, [id], body=msg, context=context)
         return True
 
     def case_cancel_send_note(self, cr, uid, ids, context=None):
         for id in ids:
             msg = _('%s has been <b>canceled</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
-            self.message_append_note(cr, uid, [id], body=msg, context=context)
+            self.message_post(cr, uid, [id], body=msg, context=context)
         return True
 
     def case_pending_send_note(self, cr, uid, ids, context=None):
         for id in ids:
             msg = _('%s is now <b>pending</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
-            self.message_append_note(cr, uid, [id], body=msg, context=context)
+            self.message_post(cr, uid, [id], body=msg, context=context)
         return True
 
     def case_reset_send_note(self, cr, uid, ids, context=None):
         for id in ids:
             msg = _('%s has been <b>renewed</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
-            self.message_append_note(cr, uid, [id], body=msg, context=context)
+            self.message_post(cr, uid, [id], body=msg, context=context)
         return True
index 3e3bdc9..8b1ba65 100644 (file)
@@ -46,21 +46,11 @@ class base_action_rule(osv.osv):
     }
 
     def email_send(self, cr, uid, obj, emails, body, emailfrom=tools.config.get('email_from', False), context=None):
-        mail_message = self.pool.get('mail.message')
-        body = self.format_mail(obj, body)
-        if not emailfrom:
-            if hasattr(obj, 'user_id') and obj.user_id and obj.user_id.email:
-                emailfrom = obj.user_id.email
-
-        name = '[%d] %s' % (obj.id, tools.ustr(obj.name))
-        emailfrom = tools.ustr(emailfrom)
-        if hasattr(obj, 'section_id') and obj.section_id and obj.section_id.alias_id:
+        mail_id = super(base_action_rule, self).email_send(cr, uid, obj, emails, body, emailfrom=emailfrom, context=context)
+        if mail_id and hasattr(obj, 'section_id') and obj.section_id and obj.section_id.alias_id:
             reply_to = obj.section_id.alias_id.name_get()[0][1]
-        else:
-            reply_to = emailfrom
-        if not emailfrom:
-            raise osv.except_osv(_('Error!'), _("There is no email for your company address."))
-        return mail_message.schedule_with_attach(cr, uid, emailfrom, emails, name, body, model=obj._name, reply_to=reply_to, res_id=obj.id)
+            self.pool.get('mail.mail').write(cr, uid, [mail_id], {'reply_to': reply_to}, context=context)
+        return mail_id
 
     def do_check(self, cr, uid, action, obj, context=None):
         ok = super(base_action_rule, self).do_check(cr, uid, action, obj, context=context)
@@ -105,8 +95,8 @@ class base_action_rule(osv.osv):
                 write['email_cc'] = obj.act_email_cc
 
         # Put state change by rule in communication history
-        if hasattr(obj, 'state') and hasattr(obj, 'message_append') and action.act_state:
-            model_obj.message_append(cr, uid, [obj], _(action.act_state))
+        if hasattr(obj, 'state') and hasattr(obj, 'message_post') and action.act_state:
+            model_obj.message_post(cr, uid, [obj], _(action.act_state), context=context)
 
         model_obj.write(cr, uid, [obj.id], write, context)
         super(base_action_rule, self).do_action(cr, uid, action, model_obj, obj, context=context)
index c567138..5bf4b5f 100644 (file)
         </record>
 
         <!-- notify all employees of module installation -->
-        <function model="mail.group" name="message_append_note">
-            <!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
-            <value eval="[ref('mail.group_all_employees')]"/>
-            <value>Module CRM has been installed</value>
-            <value>From the top menu Sales, you can: trace leads and opportunities, get accurate forecast on your sales pipeline, plan meetings and phonecalls, get realtime statistics and efficiently organize the communication with your prospects.
+        <record model="mail.message" id="module_install_notification">
+            <field name="model">mail.group</field>
+            <field name="res_id" ref="mail.group_all_employees"/>
+            <field name="type">notification</field>
+            <field name="subject">CRM application installed!</field>
+            <field name="body">From the top Sales menu you can track leads and opportunities, get accurate forecast on your sales pipeline, plan meetings and phonecalls, get realtime statistics and efficiently organize the communication with your prospects.
+To manage quotations and sale orders, install the "Sales Management" application.</field>
+        </record>
 
-To manage quotations and sale orders, install the module "Sales Management".</value>
-        </function>
-        
         <record model="mail.alias" id="default_sales_alias">
             <field name="alias_name">sales</field>
             <field name="alias_model_id" ref="model_crm_lead"/>
             <field name="alias_user_id" ref="base.user_root"/>
             <field name="alias_defaults">{'type':'lead'}</field>
         </record>
-
     </data>
 </openerp>
index 25a47ec..881c0ac 100644 (file)
 #
 ##############################################################################
 
-import binascii
 from base_status.base_stage import base_stage
 import crm
 from datetime import datetime
-from mail.mail_message import to_email
 from osv import fields, osv
 import time
 import tools
@@ -40,8 +38,7 @@ class crm_lead(base_stage, osv.osv):
     _name = "crm.lead"
     _description = "Lead/Opportunity"
     _order = "priority,date_action,id desc"
-    _inherit = ['ir.needaction_mixin', 'mail.thread']
-    _mail_compose_message = True
+    _inherit = ['mail.thread','ir.needaction_mixin']
 
     def _get_default_section_id(self, cr, uid, context=None):
         """ Gives default section by checking if present in the context """
@@ -175,16 +172,6 @@ class crm_lead(base_stage, osv.osv):
         else:
             return [('id', '=', '0')]
 
-    def _get_email_subject(self, cr, uid, ids, fields, args, context=None):
-        res = {}
-        for obj in self.browse(cr, uid, ids, context=context):
-            res[obj.id] = ''
-            for msg in obj.message_ids:
-                if msg.email_from:
-                    res[obj.id] = msg.subject
-                    break
-        return res
-
     _columns = {
         'partner_id': fields.many2one('res.partner', 'Partner', ondelete='set null',
             select=True, help="Optional linked partner, usually after conversion of the lead"),
@@ -228,7 +215,6 @@ class crm_lead(base_stage, osv.osv):
                       When the case is over, the state is set to \'Done\'.\
                       If the case needs to be reviewed then the state is \
                       set to \'Pending\'.'),
-        'subjects': fields.function(_get_email_subject, fnct_search=_history_search, string='Subject of Email', type='char', size=64),
 
         # Only used for type opportunity
         'probability': fields.float('Success Rate (%)',group_operator="avg"),
@@ -449,7 +435,7 @@ class crm_lead(base_stage, osv.osv):
         oldest_id = opportunity_ids[0]
         return self.browse(cr, uid, oldest_id, context=context)
 
-    def _mail_body_text(self, cr, uid, lead, fields, title=False, context=None):
+    def _mail_body(self, cr, uid, lead, fields, title=False, context=None):
         body = []
         if title:
             body.append("%s\n" % (title))
@@ -486,11 +472,11 @@ class crm_lead(base_stage, osv.osv):
         for opportunity in opportunities:
             subject.append(opportunity.name)
             title = "%s : %s" % (merge_message, opportunity.name)
-            details.append(self._mail_body_text(cr, uid, opportunity, fields, title=title, context=context))
+            details.append(self._mail_body(cr, uid, opportunity, fields, title=title, context=context))
 
         subject = subject[0] + ", ".join(subject[1:])
         details = "\n\n".join(details)
-        return self.message_append_note(cr, uid, [opportunity_id], subject=subject, body=details)
+        return self.message_post(cr, uid, [opportunity_id], body=details, subject=subject, context=context)
 
     def _merge_opportunity_history(self, cr, uid, opportunity_id, opportunities, context=None):
         message = self.pool.get('mail.message')
@@ -546,7 +532,7 @@ class crm_lead(base_stage, osv.osv):
         oldest = self._merge_find_oldest(cr, uid, ids, context=context)
         if ctx_opportunities :
             first_opportunity = ctx_opportunities[0]
-            tail_opportunities = opportunities_list
+            tail_opportunities = opportunities_list + ctx_opportunities[1:]
         else:
             first_opportunity = opportunities_list[0]
             tail_opportunities = opportunities_list[1:]
@@ -606,19 +592,13 @@ class crm_lead(base_stage, osv.osv):
         for lead in self.browse(cr, uid, ids, context=context):
             if lead.state in ('done', 'cancel'):
                 continue
-            if user_ids or section_id:
-                self.allocate_salesman(cr, uid, [lead.id], user_ids, section_id, context=context)
-
             vals = self._convert_opportunity_data(cr, uid, lead, customer, section_id, context=context)
             self.write(cr, uid, [lead.id], vals, context=context)
-
             self.convert_opportunity_send_note(cr, uid, lead, context=context)
-            #TOCHECK: why need to change partner details in all messages of lead ?
-            if lead.partner_id:
-                msg_ids = [ x.id for x in lead.message_ids]
-                mail_message.write(cr, uid, msg_ids, {
-                        'partner_id': lead.partner_id.id
-                    }, context=context)
+
+        if user_ids or section_id:
+            self.allocate_salesman(cr, uid, ids, user_ids, section_id, context=context)
+
         return True
 
     def _lead_create_contact(self, cr, uid, lead, name, is_company, parent_id=False, context=None):
@@ -630,7 +610,7 @@ class crm_lead(base_stage, osv.osv):
             'parent_id': parent_id,
             'phone': lead.phone,
             'mobile': lead.mobile,
-            'email': lead.email_from and to_email(lead.email_from)[0],
+            'email': lead.email_from and tools.email_split(lead.email_from)[0],
             'fax': lead.fax,
             'title': lead.title and lead.title.id or False,
             'function': lead.function,
@@ -650,7 +630,7 @@ class crm_lead(base_stage, osv.osv):
         partner_id =  False
         if lead.partner_name and lead.contact_name:
             partner_id = self._lead_create_contact(cr, uid, lead, lead.partner_name, True, context=context)
-            self._lead_create_contact(cr, uid, lead, lead.contact_name, False, partner_id, context=context)
+            partner_id = self._lead_create_contact(cr, uid, lead, lead.contact_name, False, partner_id, context=context)
         elif lead.partner_name and not lead.contact_name:
             partner_id = self._lead_create_contact(cr, uid, lead, lead.partner_name, True, context=context)
         elif not lead.partner_name and lead.contact_name:
@@ -678,32 +658,16 @@ class crm_lead(base_stage, osv.osv):
         if context is None:
             context = {}
         partner_ids = {}
+        force_partner_id = partner_id
         for lead in self.browse(cr, uid, ids, context=context):
             if action == 'create':
                 if not partner_id:
                     partner_id = self._create_lead_partner(cr, uid, lead, context)
+                partner_id = force_partner_id or self._create_lead_partner(cr, uid, lead, context=context)
             self._lead_set_partner(cr, uid, lead, partner_id, context=context)
             partner_ids[lead.id] = partner_id
         return partner_ids
 
-    def _send_mail_to_salesman(self, cr, uid, lead, context=None):
-        """
-        Send mail to salesman with updated Lead details.
-        @ lead: browse record of 'crm.lead' object.
-        """
-        #TOFIX: mail template should be used here instead of fix subject, body text.
-        message = self.pool.get('mail.message')
-        email_to = lead.user_id and lead.user_id.email
-        if not email_to:
-            return False
-
-        email_from = lead.section_id and lead.section_id.user_id and lead.section_id.user_id.email or email_to
-        partner = lead.partner_id and lead.partner_id.name or lead.partner_name
-        subject = "lead %s converted into opportunity" % lead.name
-        body = "Info \n Id : %s \n Subject: %s \n Partner: %s \n Description : %s " % (lead.id, lead.name, lead.partner_id.name, lead.description)
-        return message.schedule_with_attach(cr, uid, email_from, [email_to], subject, body)
-
-
     def allocate_salesman(self, cr, uid, ids, user_ids, team_id=False, context=None):
         index = 0
         for lead_id in ids:
@@ -821,14 +785,13 @@ class crm_lead(base_stage, osv.osv):
         if custom_values is None: custom_values = {}
         custom_values.update({
             'name':  msg.get('subject') or _("No Subject"),
-            'description': msg.get('body_text'),
+            'description': msg.get('body'),
             'email_from': msg.get('from'),
             'email_cc': msg.get('cc'),
             'user_id': False,
         })
         if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES):
             custom_values['priority'] = msg.get('priority')
-        custom_values.update(self.message_partner_by_email(cr, uid, msg.get('from', False), context=context))
         return super(crm_lead, self).message_new(cr, uid, msg, custom_values=custom_values, context=context)
 
     def message_update(self, cr, uid, ids, msg, update_vals=None, context=None):
@@ -841,18 +804,18 @@ class crm_lead(base_stage, osv.osv):
         if update_vals is None: update_vals = {}
 
         if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES):
-            vals['priority'] = msg.get('priority')
+            update_vals['priority'] = msg.get('priority')
         maps = {
             'cost':'planned_cost',
             'revenue': 'planned_revenue',
             'probability':'probability',
         }
-        for line in msg.get('body_text', '').split('\n'):
+        for line in msg.get('body', '').split('\n'):
             line = line.strip()
             res = tools.misc.command_re.match(line)
             if res and maps.get(res.group(1).lower()):
                 key = maps.get(res.group(1).lower())
-                vals[key] = res.group(2).lower()
+                update_vals[key] = res.group(2).lower()
 
         return super(crm_lead, self).message_update(cr, uid, ids, msg, update_vals=update_vals, context=context)
 
@@ -860,15 +823,10 @@ class crm_lead(base_stage, osv.osv):
     # OpenChatter methods and notifications
     # ----------------------------------------
 
-    def message_get_monitored_follower_fields(self, cr, uid, ids, context=None):
-        """ Add 'user_id' to the monitored fields """
-        res = super(crm_lead, self).message_get_monitored_follower_fields(cr, uid, ids, context=context)
-        return res + ['user_id']
-
     def stage_set_send_note(self, cr, uid, ids, stage_id, context=None):
         """ Override of the (void) default notification method. """
         stage_name = self.pool.get('crm.case.stage').name_get(cr, uid, [stage_id], context=context)[0][1]
-        return self.message_append_note(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context)
+        return self.message_post(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context)
 
     def case_get_note_msg_prefix(self, cr, uid, lead, context=None):
         if isinstance(lead, (int, long)):
@@ -878,33 +836,33 @@ class crm_lead(base_stage, osv.osv):
     def create_send_note(self, cr, uid, ids, context=None):
         for id in ids:
             message = _("%s has been <b>created</b>.")% (self.case_get_note_msg_prefix(cr, uid, id, context=context))
-            self.message_append_note(cr, uid, [id], body=message, context=context)
+            self.message_post(cr, uid, [id], body=message, context=context)
         return True
 
     def case_mark_lost_send_note(self, cr, uid, ids, context=None):
         message = _("Opportunity has been <b>lost</b>.")
-        return self.message_append_note(cr, uid, ids, body=message, context=context)
+        return self.message_post(cr, uid, ids, body=message, context=context)
 
     def case_mark_won_send_note(self, cr, uid, ids, context=None):
         message = _("Opportunity has been <b>won</b>.")
-        return self.message_append_note(cr, uid, ids, body=message, context=context)
+        return self.message_post(cr, uid, ids, body=message, context=context)
 
     def schedule_phonecall_send_note(self, cr, uid, ids, phonecall_id, action, context=None):
         phonecall = self.pool.get('crm.phonecall').browse(cr, uid, [phonecall_id], context=context)[0]
         if action == 'log': prefix = 'Logged'
         else: prefix = 'Scheduled'
         message = _("<b>%s a call</b> for the <em>%s</em>.") % (prefix, phonecall.date)
-        return self.message_append_note(cr, uid, ids, body=message, context=context)
+        return self.message_post(cr, uid, ids, body=message, context=context)
 
     def _lead_set_partner_send_note(self, cr, uid, ids, context=None):
         for lead in self.browse(cr, uid, ids, context=context):
             message = _("%s <b>partner</b> is now set to <em>%s</em>." % (self.case_get_note_msg_prefix(cr, uid, lead, context=context), lead.partner_id.name))
-            lead.message_append_note(body=message)
+            lead.message_post(body=message)
         return True
 
     def convert_opportunity_send_note(self, cr, uid, lead, context=None):
         message = _("Lead has been <b>converted to an opportunity</b>.")
-        lead.message_append_note(body=message)
+        lead.message_post(body=message)
         return True
 
 crm_lead()
index 6118c08..b3c0025 100644 (file)
@@ -552,86 +552,67 @@ Andrew</field>
             <field name="subject">Plan to buy a Laptop</field>
             <field name="model">crm.lead</field>
             <field name="res_id" ref="crm_case_15"/>
-            <field name="content_subtype">html</field>
-            <field name="body_html">&lt;![CDATA[Email0 inquiry]]&gt;&lt;div&gt;&lt;font size="2"&gt;Hello,&lt;/font&gt;&lt;/div&gt;&lt;div&gt;&lt;font size="2"&gt;&lt;br&gt;&lt;/font&gt;&lt;/div&gt;&lt;div&gt;&lt;font size="2"&gt;I am interested in your company's product and I plan to buy a new laptop having latest technologies and affordable price.&lt;/font&gt;&lt;/div&gt;&lt;div&gt;&lt;font size="2"&gt;Can you please send me product catalogue?&lt;/font&gt;&lt;/div&gt;</field>
+            <field name="body">&lt;![CDATA[Email0 inquiry]]&gt;&lt;div&gt;&lt;font size="2"&gt;Hello,&lt;/font&gt;&lt;/div&gt;&lt;div&gt;&lt;font size="2"&gt;&lt;br&gt;&lt;/font&gt;&lt;/div&gt;&lt;div&gt;&lt;font size="2"&gt;I am interested in your company's product and I plan to buy a new laptop having latest technologies and affordable price.&lt;/font&gt;&lt;/div&gt;&lt;div&gt;&lt;font size="2"&gt;Can you please send me product catalogue?&lt;/font&gt;&lt;/div&gt;</field>
             <field name="type">email</field>
-            <field name="state">received</field>
-            <field name="user_id" ref="base.user_demo"/>
         </record>
         <record id="message_note0" model="mail.message">
             <field name="subject">Re: Plan to buy a Laptop</field>
             <field name="model">crm.lead</field>
             <field name="res_id" ref="crm_case_15"/>
-            <field name="parent_id" ref="message_email0"/>
-            <field name="content_subtype">plain</field>
-            <field name="body_text">Dear Customer,
+            <field name="type">comment</field>
+            <field name="body">Dear Customer,
 Thanks for showing interest in our products.
 We have attached the catalogue,
 We would like to know your interests, Let us know if we can call you for more details.
 
 Thanks</field>
-            <field name="type">email</field>
-            <field name="user_id" ref="base.user_root"/>
-            <field name="state">sent</field>
+            <field name="parent_id" ref="message_email0"/>
+            <field name="author_id" ref="base.partner_root"/>
         </record>
         <record id="message_note0_comment0" model="mail.message">
             <field name="subject">Re: Plan to buy a Laptop</field>
             <field name="model">crm.lead</field>
             <field name="res_id" ref="crm_case_15"/>
-            <field name="content_subtype">html</field>
-            <field name="body_html">&lt;div&gt;Thanks for the information,&lt;/div&gt;&lt;div&gt;I will visit the store soon.&lt;/div&gt;</field>
+            <field name="type">comment</field>
+            <field name="body">&lt;div&gt;Thanks for the information,&lt;/div&gt;&lt;div&gt;I will visit the store soon.&lt;/div&gt;</field>
             <field name="parent_id" ref="message_note0"/>
-            <field name="type">email</field>
-            <field name="user_id" ref="base.user_demo"/>
-            <field name="state">received</field>
+            <field name="author_id" ref="base.partner_demo"/>
         </record>
         <record id="message_note0_comment1" model="mail.message">
             <field name="subject">Re: Plan to buy a Laptop</field>
             <field name="model">crm.lead</field>
             <field name="res_id" ref="crm_case_15"/>
-            <field name="content_subtype">html</field>
-            <field name="body_html">&lt;font color="#1f1f1f"&gt;Can you tell me if the store is open at 9:00 PM?&lt;/b&gt;&lt;/font&gt;</field>
+            <field name="type">comment</field>
+            <field name="body">&lt;font color="#1f1f1f"&gt;Can you tell me if the store is open at 9:00 PM?&lt;/b&gt;&lt;/font&gt;</field>
             <field name="parent_id" ref="message_note0"/>
-            <field name="type">email</field>
-            <field name="state">received</field>
-            <field name="user_id" ref="base.user_demo"/>
+            <field name="author_id" ref="base.partner_demo"/>
         </record>
         <record id="message_email1" model="mail.message">
             <field name="subject">Re: Plan to buy a Laptop</field>
             <field name="model">crm.lead</field>
             <field name="res_id" ref="crm_case_15"/>
-            <field name="content_subtype">plain</field>
-            <field name="body_text">Yes, its open till 10:00 PM, you are welcome!</field>
+            <field name="body">Yes, its open till 10:00 PM, you are welcome!</field>
             <field name="type">email</field>
-            <field name="state">sent</field>
-            <field name="user_id" ref="base.user_root"/>
+            <field name="author_id" ref="base.partner_root"/>
         </record>
-
         <record id="message_email_12" model="mail.message">
             <field name="subject">Inquiry</field>
             <field name="model">crm.lead</field>
             <field name="res_id" ref="crm_case_1"/>
-            <field name="content_subtype">plain</field>
-            <field name="body_text">Hello,
+            <field name="body">Hello,
 I am Jason from Le Club SARL,
 I am intertested to attend Training organized in your company,
 Can you send details,</field>
             <field name="type">email</field>
-            <field name="state">received</field>
-            <field name="user_id" ref="base.user_demo"/>
         </record>
-
         <record id="message_email_13" model="mail.message">
             <field name="subject">Need Details</field>
             <field name="model">crm.lead</field>
             <field name="res_id" ref="crm_case_2"/>
-            <field name="content_subtype">plain</field>
-            <field name="body_text">Want to know features and benifits to use the new software.</field>
+            <field name="body">Want to know features and benifits to use the new software.</field>
             <field name="type">comment</field>
-            <field name="user_id" ref="base.user_demo"/>
         </record>
 
-
         <!--        Call Function to set the opportunities as Unread -->
         <function model="crm.lead" name="message_mark_as_unread"
             eval="[ ref('crm_case_15'), ref('crm_case_16'),
index 1324340..f763f0a 100644 (file)
             <field name="name">Leads</field>
             <field name="model">crm.lead</field>
             <field name="arch" type="xml">
-                <tree string="Leads" fonts="bold:needaction_pending==True" colors="grey:state in ('cancel', 'done')">
+                <tree string="Leads" fonts="bold:message_unread==True" colors="grey:state in ('cancel', 'done')">
                     <field name="date_deadline" invisible="1"/>
                     <field name="create_date" groups="base.group_no_one"/>
                     <field name="name"/>
                     <field name="type_id" invisible="1"/>
                     <field name="referred" invisible="1"/>
                     <field name="channel_id" invisible="1"/>
-                    <field name="subjects" invisible="1"/>
-                    <field name="needaction_pending" invisible="1"/>
+                    <field name="message_unread" invisible="1"/>
                 </tree>
             </field>
         </record>
                     <field name="user_id"/>
                     <field name="partner_address_email"/>
                     <field name="message_summary"/>
-                    <field name="needaction_pending"/>
+                    <field name="message_unread"/>
                     <templates>
                         <t t-name="lead_details">
                             <ul class="oe_kanban_tooltip oe_semantic_html_override">
                                         <img t-att-src="kanban_image('res.users', 'image_small', record.user_id.raw_value)" t-att-title="record.user_id.value" width="24" height="24" class="oe_kanban_avatar"/>
                                     </div>
                                     <div class="oe_kanban_footer_left">
-                                        <t t-if="record.needaction_pending.raw_value"><span class="oe_kanban_mail_new">New</span></t>
                                         <t t-raw="record.message_summary.raw_value"/>
                                     </div>
                                 </div>
                 <search string="Search Leads">
                     <field name="name" string="Lead / Customer" filter_domain="['|','|',('partner_name','ilike',self),('email_from','ilike',self),('name','ilike',self)]"/>
                     <field name="categ_ids" string="Category" filter_domain="[('categ_ids','ilike',self)]" />
-                    <!-- subjects is not set as store=True so, it is placed outside filter_domain-->
-                    <field name="subjects"/>
                     <field name="create_date"/>
-                    <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="needaction_pending" domain="[('needaction_pending','=',True)]"/>
+                    <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/>
                     <separator/>
                     <filter icon="terp-check" string="New" name="new" help="New Leads" domain="[('state','=','draft')]"/>
                     <filter icon="terp-camera_test" string="Open" name="open" domain="[('state','=','open')]"/>
             <field name="name">Opportunities Tree</field>
             <field name="model">crm.lead</field>
             <field name="arch" type="xml">
-                <tree string="Opportunities" fonts="bold:needaction_pending==True" colors="gray:state in ('cancel', 'done');red:date_deadline and (date_deadline &lt; current_date)">
+                <tree string="Opportunities" fonts="bold:message_unread==True" colors="gray:state in ('cancel', 'done');red:date_deadline and (date_deadline &lt; current_date)">
                     <field name="date_deadline" invisible="1"/>
                     <field name="create_date" groups="base.group_no_one"/>
                     <field name="name" string="Opportunity"/>
                     <field name="title_action" />
                     <field name="channel_id" invisible="1"/>
                     <field name="type_id" invisible="1"/>
-                    <field name="subjects" invisible="1"/>
                     <field name="stage_id"/>
                     <field name="planned_revenue" sum="Expected Revenues"/>
                     <field name="probability" widget="progressbar" avg="Avg. of Probability"/>
                     <field name="user_id"/>
                     <field name="priority" invisible="1"/>
                     <field name="state" groups="base.group_no_one"/>
-                    <field name="needaction_pending" invisible="1"/>
+                    <field name="message_unread" invisible="1"/>
                 </tree>
             </field>
         </record>
                     <field name="name" string="Opportunity / Customer"
                         filter_domain="['|','|','|',('partner_id','ilike',self),('partner_name','ilike',self),('email_from','ilike',self),('name', 'ilike', self)]"/>
                     <field name="categ_ids" string="Category" filter_domain="[('categ_ids','ilike', self)]" />
-                    <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="needaction_pending" domain="[('needaction_pending','=',True)]"/>
+                    <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/>
                     <separator/>
                     <filter icon="terp-check" string="New" help="New Opportunities" name="new" domain="[('state','=','draft')]"/>
                     <filter icon="terp-camera_test" string="Open" help="Open Opportunities" name="open" domain="[('state','=','open')]"/>
index 13171d9..62fc533 100644 (file)
@@ -44,7 +44,7 @@ class crm_meeting(osv.Model):
     def create_send_note(self, cr, uid, ids, context=None):
         if context is None:
             context = {}
-        # update context: if come from phonecall, default state values can make the message_append_note crash
+        # update context: if come from phonecall, default state values can make the message_post crash
         context.pop('default_state', False)
         for meeting in self.browse(cr, uid, ids, context=context):
             # in the message, transpose meeting.date to the timezone of the current user
@@ -53,14 +53,14 @@ class crm_meeting(osv.Model):
             if meeting.opportunity_id: # meeting can be create from phonecalls or opportunities, therefore checking for the parent
                 lead = meeting.opportunity_id
                 message = _("Meeting linked to the opportunity <em>%s</em> has been <b>created</b> and <b>scheduled</b> on <em>%s</em>.") % (lead.name, meeting_date_tz)
-                lead.message_append_note(_('System Notification'), message)
+                lead.message_post(body=message)
             elif meeting.phonecall_id:
                 phonecall = meeting.phonecall_id
                 message = _("Meeting linked to the phonecall <em>%s</em> has been <b>created</b> and <b>scheduled</b> on <em>%s</em>.") % (phonecall.name, meeting_date_tz)
-                phonecall.message_append_note(body=message)
+                phonecall.message_post(body=message)
             else:
                 message = _("A meeting has been <b>scheduled</b> on <em>%s</em>.") % (meeting_date_tz)
-            meeting.message_append_note(body=message)
+            meeting.message_post(body=message)
         return True
 
 class calendar_attendee(osv.osv):
index da08f97..44894fc 100644 (file)
@@ -32,7 +32,7 @@ class crm_phonecall(base_state, osv.osv):
     _name = "crm.phonecall"
     _description = "Phonecall"
     _order = "id desc"
-    _inherit = ['ir.needaction_mixin', 'mail.thread']
+    _inherit = ['mail.thread']
     _columns = {
         # base_state required fields
         'date_action_last': fields.datetime('Last Action', readonly=1),
@@ -177,11 +177,11 @@ class crm_phonecall(base_state, osv.osv):
         if context is None:
             context = {}
         partner_ids = {}
+        force_partner_id = partner_id
         for call in self.browse(cr, uid, ids, context=context):
             if action == 'create':
-               if not partner_id:
-                   partner_id = self._call_create_partner(cr, uid, call, context=context)
-               self._call_create_partner_address(cr, uid, call, partner_id, context=context)
+                partner_id = force_partner_id or self._call_create_partner(cr, uid, call, context=context)
+                self._call_create_partner_address(cr, uid, call, partner_id, context=context)
             self._call_set_partner(cr, uid, [call.id], partner_id, context=context)
             partner_ids[call.id] = partner_id
         return partner_ids
@@ -266,7 +266,7 @@ class crm_phonecall(base_state, osv.osv):
     
     def case_reset_send_note(self, cr, uid, ids, context=None):
         message = _('Phonecall has been <b>reset and set as open</b>.')
-        return self.message_append_note(cr, uid, ids, body=message, context=context)
+        return self.message_post(cr, uid, ids, body=message, context=context)
 
     def case_open_send_note(self, cr, uid, ids, context=None):
         lead_obj = self.pool.get('crm.lead')
@@ -280,11 +280,11 @@ class crm_phonecall(base_state, osv.osv):
                 message = _("Phonecall linked to the opportunity <em>%s</em> has been <b>created</b> and <b>scheduled</b> on <em>%s</em>.") % (lead.name, phonecall_date_str)
             else:
                 message = _("Phonecall has been <b>created and opened</b>.")
-            phonecall.message_append_note(body=message)
+            phonecall.message_post(body=message)
         return True
 
     def _call_set_partner_send_note(self, cr, uid, ids, context=None):
-        return self.message_append_note(cr, uid, ids, body=_("Partner has been <b>created</b>."), context=context)
+        return self.message_post(cr, uid, ids, body=_("Partner has been <b>created</b>."), context=context)
 
 
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
index 3b51622..2f885e0 100644 (file)
@@ -66,8 +66,7 @@
         <field name="name">CRM - Phone Calls Tree</field>
         <field name="model">crm.phonecall</field>
         <field name="arch" type="xml">
-            <tree fonts="bold:needaction_pending==True" colors="gray:state in ('cancel','done');blue:state in ('pending',)" string="Phone Calls">
-                <field name="needaction_pending" invisible="1"/>
+            <tree colors="gray:state in ('cancel','done');blue:state in ('pending',)" string="Phone Calls">
                 <field name="date"/>
                 <field name="name"/>
                 <field name="partner_id"/>
@@ -76,7 +75,6 @@
                 <field name="categ_id" invisible="1"/>
                 <field name="create_date" invisible="1"/>
                 <field name="opportunity_id" invisible="1"/>
-                <field name="needaction_pending" invisible="1"/>
                 <button string="Convert to Opportunity"
                     name="%(phonecall2opportunity_act)d"
                     states="open,pending"
         <field name="name">CRM - Logged Phone Calls Tree</field>
         <field name="model">crm.phonecall</field>
         <field name="arch" type="xml">
-            <tree string="Phone Calls" fonts="bold:needaction_pending==True" editable="top">
+            <tree string="Phone Calls" editable="top">
                 <field name="date"/>
                 <field name="name"/>
                 <field name="partner_id"
                 <field name="state" invisible="1"/>
                 <field name="create_date" invisible="1"/>
                 <field name="opportunity_id" invisible="1"/>
-                <field name="needaction_pending" invisible="1"/>
                 <button string="Schedule Other Call"
                     icon="terp-call-start"
                     name="%(phonecall_to_phonecall_act)d"
             <search string="Search Phonecalls">
                <field name="name" string="Phonecalls"/>
                <field name="date"/>
-               <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="needaction_pending" domain="[('needaction_pending','=',True)]"/>
                <separator/>
                <filter icon="terp-gtk-go-back-rtl" string="To Do" name="current" domain="[('state','=','open')]"/>
                <separator/>
index 1653be1..d18fa54 100644 (file)
@@ -23,7 +23,7 @@
   !python {model: mail.compose.message}: |
     lead_ids = self.pool.get('crm.lead').search(cr, uid, [('email_from','=', 'Mr. John Right <info@customer.com>')])
     context.update({'active_model': 'crm.lead','active_id': lead_ids[0]})
-    id = self.create(cr, uid, {'body_text': "Merci à l'intérêt pour notre produit.nous vous contacterons bientôt. Merci", 'email_from': 'sales@mycompany.com'}, context=context)
+    id = self.create(cr, uid, {'body': "Merci à l'intérêt pour notre produit.nous vous contacterons bientôt. Merci", 'email_from': 'sales@mycompany.com'}, context=context)
     try:
         self.send_mail(cr, uid, [id], context=context)
     except:
     lead_ids = self.search(cr, uid, [('email_from','=', 'Mr. John Right <info@customer.com>')])
     self.convert_partner(cr, uid, lead_ids, context=context)
 -
-  Now, I search customer in regular customer list.
--
-  !python {model: crm.lead}: |
-    partner_ids = self.message_partner_by_email(cr, uid, 'Mr. John Right <info@customer.com>')
-    assert partner_ids.get('partner_id'), "Customer is not found in regular customer list."
--
   I convert one phonecall request as a customer and put into regular customer list. 
 -
   !python {model: crm.phonecall2partner}: |
index 23713df..ed55242 100644 (file)
@@ -55,7 +55,7 @@
   After communicated  with customer, I put some notes with contract details.
 -
   !python {model: crm.lead}: |
-    self.message_append_note(cr, uid, [ref('crm_case_4')], subject='Test note', body='ces détails envoyés par le client sur ​​le FAX pour la qualité')
+    self.message_post(cr, uid, [ref('crm_case_4')], subject='Test note', body='ces détails envoyés par le client sur ​​le FAX pour la qualité')
 -
   I win this opportunity
 -
@@ -73,7 +73,7 @@
   I convert mass lead into opportunity customer.
 -
   !python {model: crm.lead2opportunity.partner.mass}: |
-    context.update({'active_model': 'crm.lead', 'active_ids': [ref("crm_case_11"), ref("crm_case_2")], 'active_id': ref("crm_case_4")}) 
+    context.update({'active_model': 'crm.lead', 'active_ids': [ref("crm_case_11"), ref("crm_case_2")], 'active_id': ref("crm_case_11")}) 
     id = self.create(cr, uid, {'user_ids': [ref('base.user_root')], 'section_id': ref('crm.section_sales_department')}, context=context)
     self.mass_convert(cr, uid, [id], context=context)
 -
@@ -83,7 +83,8 @@
     opp = self.browse(cr, uid, ref('crm_case_11'))
     assert opp.name == "Need estimated cost for new project", "Opportunity name not correct"
     assert opp.type == 'opportunity', 'Lead is not converted to opportunity!'
-    assert opp.partner_id.name == "Thomas Passot", 'Partner mismatch!'
+    expected_partner = "Thomas Passot"
+    assert opp.partner_id.name == expected_partner, 'Partner mismatch! %s vs %s' % (opp.partner_id.name, expected_partner)
     assert opp.stage_id.id == ref("stage_lead1"), 'Stage of probability is incorrect!'
 -
   Then check for second lead converted on opportunity.
index 547d3ed..e90303d 100644 (file)
@@ -24,8 +24,6 @@ from tools.translate import _
 import tools
 import re
 
-import time
-
 class crm_lead2opportunity_partner(osv.osv_memory):
     _name = 'crm.lead2opportunity.partner'
     _description = 'Lead To Opportunity Partner'
@@ -35,8 +33,8 @@ class crm_lead2opportunity_partner(osv.osv_memory):
         'action': fields.selection([('exist', 'Link to an existing partner'), \
                                     ('create', 'Create a new partner'), \
                                     ('nothing', 'Do not link to a partner')], \
-                                    'Action', required=True),
-        'name': fields.selection([('convert', 'Convert to Opportunity'), ('merge', 'Merge with existing Opportunity')],'Select Action', required=True),
+                                    'Related Partner', required=True),
+        'name': fields.selection([('convert', 'Convert to Opportunities'), ('merge', 'Merge with existing Opportunities')], 'Conversion Action', required=True),
         'opportunity_ids': fields.many2many('crm.lead', string='Opportunities', domain=[('type', '=', 'opportunity')]),
     }
 
@@ -68,8 +66,6 @@ class crm_lead2opportunity_partner(osv.osv_memory):
             if ids:
                 opportunities.append(ids[0])
         if not partner_id:
-            label = False
-            opp_ids = []
             if email:
                 # Find email of existing opportunity matches the email_from of the lead
                 cr.execute("""select id from crm_lead where type='opportunity' and
@@ -106,23 +102,36 @@ class crm_lead2opportunity_partner(osv.osv_memory):
         if context is None:
             context = {}
         lead = self.pool.get('crm.lead')
-        partner_id = self._create_partner(cr, uid, ids, context=context)
+        res = False
+        partner_ids_map = self._create_partner(cr, uid, ids, context=context)
         lead_ids = vals.get('lead_ids', [])
-        user_ids = vals.get('user_ids', False)
         team_id = vals.get('section_id', False)
-        return lead.convert_opportunity(cr, uid, lead_ids, partner_id, user_ids, team_id, context=context)
+        for lead_id in lead_ids:
+            partner_id = partner_ids_map.get(lead_id, False)
+            # FIXME: cannot pass user_ids as the salesman allocation only works in batch
+            res = lead.convert_opportunity(cr, uid, [lead_id], partner_id, [], team_id, context=context)
+        # FIXME: must perform salesman allocation in batch separately here  
+        user_ids = vals.get('user_ids', False)
+        if user_ids:
+            lead.allocate_salesman(cr, uid, lead_ids, user_ids, team_id=team_id, context=context)
+        return res
 
     def _merge_opportunity(self, cr, uid, ids, opportunity_ids, action='merge', context=None):
-        #TOFIX: is it usefully ?
         if context is None:
             context = {}
-        merge_opportunity = self.pool.get('crm.merge.opportunity')
         res = False
-        #If we convert in mass, don't merge if there is no other opportunity but no warning
-        if action == 'merge' and (len(opportunity_ids) > 1 or not context.get('mass_convert') ):
-            self.write(cr, uid, ids, {'opportunity_ids' : [(6,0, [opportunity_ids[0].id])]}, context=context)
-            context.update({'lead_ids' : record_id, "convert" : True})
-            res = merge_opportunity.merge(cr, uid, data.opportunity_ids, context=context)
+        # Expected: all newly-converted leads (active_ids) will be merged with the opportunity(ies)
+        # that have been selected in the 'opportunity_ids' m2m, with all these records
+        # merged into the first opportunity (and the rest deleted)
+        opportunity_ids = [o.id for o in opportunity_ids]
+        lead_ids = context.get('active_ids', [])
+        if action == 'merge' and lead_ids and opportunity_ids:
+            # Add the leads in the to-merge list, next to other opps
+            # (the fact that they're passed in context['lead_ids'] means that
+            # they cannot be selected to contain the result of the merge.
+            opportunity_ids.extend(lead_ids)
+            context.update({'lead_ids': lead_ids, "convert" : True})
+            res = self.pool.get('crm.lead').merge_opportunity(cr, uid, opportunity_ids, context=context)
         return res
 
     def action_apply(self, cr, uid, ids, context=None):
@@ -131,27 +140,37 @@ class crm_lead2opportunity_partner(osv.osv_memory):
         """
         if not context:
             context = {}
-
         lead = self.pool.get('crm.lead')
         lead_ids = context.get('active_ids', [])
         data = self.browse(cr, uid, ids, context=context)[0]
         self._convert_opportunity(cr, uid, ids, {'lead_ids': lead_ids}, context=context)
-        self._merge_opportunity(cr, uid, ids, data.opportunity_ids, data.action, context=context)
+        self._merge_opportunity(cr, uid, ids, data.opportunity_ids, data.name, context=context)
         return lead.redirect_opportunity_view(cr, uid, lead_ids[0], context=context)
 
-crm_lead2opportunity_partner()
 
 class crm_lead2opportunity_mass_convert(osv.osv_memory):
     _name = 'crm.lead2opportunity.partner.mass'
     _description = 'Mass Lead To Opportunity Partner'
     _inherit = 'crm.lead2opportunity.partner'
 
-
     _columns = {
-            'user_ids':  fields.many2many('res.users', string='Salesmans'),
+            'user_ids':  fields.many2many('res.users', string='Salesmen'),
             'section_id': fields.many2one('crm.case.section', 'Sales Team'),
-
     }
+
+    def default_get(self, cr, uid, fields, context=None):
+        res = super(crm_lead2opportunity_mass_convert, self).default_get(cr, uid, fields, context)
+        if 'partner_id' in fields:
+            # avoid forcing the partner of the first lead as default
+            res['partner_id'] = False
+        if 'action' in fields:
+            res['action'] = 'create'
+        if 'name' in fields:
+            res['name'] = 'convert'
+        if 'opportunity_ids' in fields:
+            res['opportunity_ids'] = False
+        return res
+
     def _convert_opportunity(self, cr, uid, ids, vals, context=None):
         data = self.browse(cr, uid, ids, context=context)[0]
         salesteam_id = data.section_id and data.section_id.id or False
@@ -162,9 +181,6 @@ class crm_lead2opportunity_mass_convert(osv.osv_memory):
         return super(crm_lead2opportunity_mass_convert, self)._convert_opportunity(cr, uid, ids, vals, context=context)
 
     def mass_convert(self, cr, uid, ids, context=None):
-        value = self.default_get(cr, uid, ['partner_id', 'opportunity_ids'], context=context)
-        value['opportunity_ids'] = [(6, 0, value['opportunity_ids'])]
-        self.write(cr, uid, ids, value, context=context)
         return self.action_apply(cr, uid, ids, context=context)
-crm_lead2opportunity_mass_convert()
+
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
index 8829749..6a826f7 100644 (file)
             <field name="model">crm.lead2opportunity.partner.mass</field>
             <field name="arch" type="xml">
                 <form string="Convert to Opportunity" version="7.0">
-                    <field name="action"/>
-                    <field name="name" colspan="4"/>
-                    <group string="Assigned Opportunities to">
-                        <field name="section_id"/>
+                    <group string="Conversion Options">
+                        <field name="action"/>
+                        <field name="partner_id" attrs="{'invisible':[('action','!=','exist')],'required': [('action', '=', 'exist')]}"/>
+                        <field name="name"/>
                     </group>
-                    <separator string="Select Salesman"/>
-                    <field name="user_ids">
+                    <group string="Select Opportunities" attrs="{'invisible': [('name', '=', 'convert')]}">
+                    <field name="opportunity_ids" colspan="4" nolabel="1" attrs="{'invisible': [('name', '=', 'convert')]}">
                         <tree>
-                            <field name="name"/>
+                            <field name="name" />
+                            <field name="partner_id" />
+                            <field name="user_id" />
+                            <field name="section_id" />
                         </tree>
                     </field>
+                    </group>
+                    
+                    <group string="Assign opportunities to">
+                        <field name="section_id" />
+                        <field name="user_ids" colspan="4">
+                            <tree>
+                                <field name="name" />
+                            </tree>
+                        </field>
+                    </group>
+                    
                     <footer>
-                        <button name="mass_convert" string="Convert into Opportunities" type="object" class="oe_highlight"/>
+                        <button name="mass_convert" string="Convert to Opportunities" type="object" class="oe_highlight"/>
                         or
                         <button string="Cancel" class="oe_link" special="cancel"/>
-                    </footer>
+                    </footer>                            
                 </form>
-            </field>
+              </field>
         </record>
 
         <record id="action_crm_lead2opportunity_partner" model="ir.actions.act_window">
index 98f114b..9b239ba 100644 (file)
@@ -50,20 +50,20 @@ class crm_lead2partner(osv.osv_memory):
     def _select_partner(self, cr, uid, context=None):
         if context is None:
             context = {}
-        lead = self.pool.get('crm.lead')
-        partner = self.pool.get('res.partner')
-        lead_ids = list(context and context.get('active_ids', []) or [])
-        if not len(lead_ids):
+        if not context.get('active_model') == 'crm.lead' or not context.get('active_id'):
             return False
-        this = lead.browse(cr, uid, lead_ids[0], context=context)
-        # Find partner address matches the email_from of the lead
-        res = lead.message_partner_by_email(cr, uid, this.email_from, context=context)
-        partner_id = res.get('partner_id', False)      
-        # Find partner name that matches the name of the lead
-        if not partner_id and this.partner_name:
+        partner = self.pool.get('res.partner')
+        lead = self.pool.get('crm.lead')
+        this = lead.browse(cr, uid, context.get('active_id'), context=context)
+        partner_id = False
+        if this.email_from:
+            partner_ids = partner.search(cr, uid, [('email', '=', this.email_from)], context=context)
+            if partner_ids:
+                partner_id = partner_ids[0]
+        if not this.partner_id and this.partner_name:
             partner_ids = partner.search(cr, uid, [('name', '=', this.partner_name)], context=context)
-            if partner_ids and len(partner_ids):
-               partner_id = partner_ids[0]
+            if partner_ids:
+                partner_id = partner_ids[0]
         return partner_id
 
     def default_get(self, cr, uid, fields, context=None):
@@ -107,15 +107,16 @@ class crm_lead2partner(osv.osv_memory):
         lead_ids = context and context.get('active_ids') or []
         data = self.browse(cr, uid, ids, context=context)[0]
         partner_id = data.partner_id and data.partner_id.id or False
-        partner_ids = lead.convert_partner(cr, uid, lead_ids, data.action, partner_id, context=context)
-        return partner_ids[lead_ids[0]]
+        return lead.convert_partner(cr, uid, lead_ids, data.action, partner_id, context=context)
 
     def make_partner(self, cr, uid, ids, context=None):
         """
         This function Makes partner based on action.
         """
-        partner_id = self._create_partner(cr, uid, ids, context=context)
-        return self.pool.get('res.partner').redirect_partner_form(cr, uid, partner_id, context=context)
+        # Only called from Form view, so only meant to convert  one Lead. 
+        lead_id = context and context.get('active_id') or False
+        partner_ids_map = self._create_partner(cr, uid, ids, context=context)
+        return self.pool.get('res.partner').redirect_partner_form(cr, uid, partner_ids_map.get(lead_id, False), context=context)
 
 crm_lead2partner()
 
index 422af02..715b739 100644 (file)
@@ -57,12 +57,10 @@ class crm_phonecall2partner(osv.osv_memory):
         if context is None:
             context = {}
         phonecall = self.pool.get('crm.phonecall')
-
         data = self.browse(cr, uid, ids, context=context)[0]
         call_ids = context and context.get('active_ids') or []
         partner_id = data.partner_id and data.partner_id.id or False
-        partner_ids = phonecall.convert_partner(cr, uid, call_ids, data.action, partner_id, context=context)
-        return partner_ids[call_ids[0]]
+        return phonecall.convert_partner(cr, uid, call_ids, data.action, partner_id, context=context)
 
 crm_phonecall2partner()
 
index ec6252b..535f6d2 100644 (file)
@@ -72,7 +72,7 @@ class crm_claim(base_stage, osv.osv):
     _description = "Claim"
     _order = "priority,date desc"
     _inherit = ['mail.thread']
-    _mail_compose_message = True
+
     _columns = {
         'id': fields.integer('ID', readonly=True),
         'name': fields.char('Claim Subject', size=128, required=True),
@@ -194,13 +194,12 @@ class crm_claim(base_stage, osv.osv):
         if custom_values is None: custom_values = {}
         custom_values.update({
             'name': msg.get('subject') or _("No Subject"),
-            'description': msg.get('body_text'),
+            'description': msg.get('body'),
             'email_from': msg.get('from'),
             'email_cc': msg.get('cc'),
         })
         if msg.get('priority'):
             custom_values['priority'] = msg.get('priority')
-        custom_values.update(self.message_partner_by_email(cr, uid, msg.get('from'), context=context))
         return super(crm_claim,self).message_new(cr, uid, msg, custom_values=custom_values, context=context)
 
     def message_update(self, cr, uid, ids, msg, update_vals=None, context=None):
@@ -220,7 +219,7 @@ class crm_claim(base_stage, osv.osv):
             'revenue': 'planned_revenue',
             'probability':'probability'
         }
-        for line in msg['body_text'].split('\n'):
+        for line in msg['body'].split('\n'):
             line = line.strip()
             res = tools.misc.command_re.match(line)
             if res and maps.get(res.group(1).lower()):
@@ -239,16 +238,16 @@ class crm_claim(base_stage, osv.osv):
 
     def create_send_note(self, cr, uid, ids, context=None):
         msg = _('Claim has been <b>created</b>.')
-        return self.message_append_note(cr, uid, ids, body=msg, context=context)
+        return self.message_post(cr, uid, ids, body=msg, context=context)
 
     def case_refuse_send_note(self, cr, uid, ids, context=None):
         msg = _('Claim has been <b>refused</b>.')
-        return self.message_append_note(cr, uid, ids, body=msg, context=context)
+        return self.message_post(cr, uid, ids, body=msg, context=context)
 
     def stage_set_send_note(self, cr, uid, ids, stage_id, context=None):
         """ Override of the (void) default notification method. """
         stage_name = self.pool.get('crm.claim.stage').name_get(cr, uid, [stage_id], context=context)[0][1]
-        return self.message_append_note(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context)
+        return self.message_post(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context)
 
 
 class res_partner(osv.osv):
index ad8fdf3..044e048 100644 (file)
@@ -9,7 +9,6 @@
 -
   !python {model: crm.claim}: |
     try:
-      self.message_update(cr, uid,[ref('crm_claim_4')], {'subject': 'Claim Update record','body_text': 'first training session completed',})
+      self.message_update(cr, uid,[ref('crm_claim_4')], {'subject': 'Claim Update record','body': 'first training session completed',})
     except:
       pass
-  
\ No newline at end of file
index 4b0cb2e..612537b 100644 (file)
@@ -38,7 +38,7 @@ class crm_helpdesk(base_state, osv.osv):
     _description = "Helpdesk"
     _order = "id desc"
     _inherit = ['mail.thread']
-    _mail_compose_message = True
+
     _columns = {
             'id': fields.integer('ID', readonly=True),
             'name': fields.char('Name', size=128, required=True),
@@ -105,12 +105,11 @@ class crm_helpdesk(base_state, osv.osv):
         if custom_values is None: custom_values = {}
         custom_values.update({
             'name': msg.get('subject') or _("No Subject"),
-            'description': msg.get('body_text'),
+            'description': msg.get('body'),
             'email_from': msg.get('from'),
             'email_cc': msg.get('cc'),
             'user_id': False,
         })
-        custom_values.update(self.message_partner_by_email(cr, uid, msg.get('from'), context=context))
         return super(crm_helpdesk,self).message_new(cr, uid, msg, custom_values=custom_values, context=context)
 
     def message_update(self, cr, uid, ids, msg, update_vals=None, context=None):
@@ -130,7 +129,7 @@ class crm_helpdesk(base_state, osv.osv):
             'revenue': 'planned_revenue',
             'probability':'probability'
         }
-        for line in msg['body_text'].split('\n'):
+        for line in msg['body'].split('\n'):
             line = line.strip()
             res = tools.misc.command_re.match(line)
             if res and maps.get(res.group(1).lower()):
@@ -149,7 +148,7 @@ class crm_helpdesk(base_state, osv.osv):
 
     def create_send_note(self, cr, uid, ids, context=None):
         msg = _('Case has been <b>created</b>.')
-        self.message_append_note(cr, uid, ids, body=msg, context=context)
+        self.message_post(cr, uid, ids, body=msg, context=context)
         return True
 
 
index 93a33d6..6496935 100644 (file)
@@ -97,7 +97,7 @@ class crm_helpdesk_report(osv.osv):
                     c.planned_cost,
                     count(*) as nbr,
                     extract('epoch' from (c.date_closed-c.create_date))/(3600*24) as  delay_close,
-                    (SELECT count(id) FROM mail_message WHERE model='crm.helpdesk' AND res_id=c.id AND email_from IS NOT NULL) AS email,
+                    (SELECT count(id) FROM mail_message WHERE model='crm.helpdesk' AND res_id=c.id AND type = 'email') AS email,
                     abs(avg(extract('epoch' from (c.date_deadline - c.date_closed)))/(3600*24)) as delay_expected
                 from
                     crm_helpdesk c
index 320bd61..4fd6a9e 100644 (file)
@@ -23,7 +23,7 @@
   !python {model: crm.helpdesk}: |
     question_ids = self.search(cr, uid, [('email_from','=', 'Mr. John Right <info@customer.com>')])
     try:
-      self.message_update(cr, uid, question_ids, {'subject': 'Link of product', 'body_text': 'www.openerp.com'})
+      self.message_update(cr, uid, question_ids, {'subject': 'Link of product', 'body': 'www.openerp.com'})
     except:
       pass
 
index d4ff93d..cd2e3f0 100644 (file)
@@ -19,8 +19,7 @@
                                             attrs="{'invisible':[('partner_assigned_id','=',False)]}"
                                             name="%(crm_lead_forward_to_partner_act)d"
                                             icon="terp-mail-forward" type="action"
-                                            context="{'default_name': 'partner', 'default_partner_id': partner_assigned_id}"
-                                        />
+                                            context="{'default_composition_mode': 'forward', 'default_partner_ids': [partner_assigned_id]}"/>
                                     </div>
                                 </group>
                                 <group string="Geo Assignation">
@@ -37,8 +36,6 @@
             </field>
         </record>
 
-
-
         <record id="view_crm_opportunity_geo_assign_tree" model="ir.ui.view">
             <field name="name">crm.lead.geo_assign.tree.inherit</field>
             <field name="model">crm.lead</field>
@@ -49,6 +46,7 @@
                  </field>
             </field>
         </record>
+
         <record model="ir.ui.view" id="crm_opportunity_partner_filter">
             <field name="name">crm.opportunity.partner.filter.assigned</field>
             <field name="model">crm.lead</field>
@@ -63,5 +61,6 @@
                 </field>
             </field>
          </record>
+
     </data>
 </openerp>
index 3fb9fc0..c3ddc66 100644 (file)
@@ -72,7 +72,7 @@ msgid "Geo Localize"
 msgstr ""
 
 #. module: crm_partner_assign
-#: help:crm.lead.forward.to.partner,body_text:0
+#: help:crm.lead.forward.to.partner,body:0
 msgid "Plain-text version of the message"
 msgstr ""
 
@@ -129,7 +129,7 @@ msgid "Highest"
 msgstr ""
 
 #. module: crm_partner_assign
-#: field:crm.lead.forward.to.partner,body_text:0
+#: field:crm.lead.forward.to.partner,body:0
 msgid "Text contents"
 msgstr ""
 
index bc4cd0d..a055fc6 100644 (file)
@@ -29,8 +29,8 @@
   I forward this opportunity to its nearest partner.
 -
   !python {model: crm.lead.forward.to.partner}: |
-    context.update({'active_model': 'crm.lead', 'active_id': ref('crm.crm_case_19'), 'active_ids': [ref('crm.crm_case_19')]})
-    forward_id = self.create(cr, uid, {'email_from': 'test@openerp.com', 'send_to': 'partner'}, context=context)
+    context.update({'default_model': 'crm.lead', 'default_res_id': ref('crm.crm_case_19'), 'active_ids': [ref('crm.crm_case_19')]})
+    forward_id = self.create(cr, uid, {}, context=context)
     try:
       self.action_forward(cr, uid, [forward_id], context=context)
     except:
index d479f8b..0cac38e 100644 (file)
 #
 ##############################################################################
 
-import time
 import re
+import time
+import tools
+
 from osv import osv, fields
 from tools.translate import _
-from mail.mail_message import to_email
 
 class crm_lead_forward_to_partner(osv.osv_memory):
-    """Forwards lead history"""
+    """ Forward info history to partners. """
     _name = 'crm.lead.forward.to.partner'
     _inherit = "mail.compose.message"
 
+    def default_get(self, cr, uid, fields, context=None):
+        if context is None:
+            context = {}
+        # set as comment, perform overrided document-like action that calls get_record_data
+        old_mode = context.get('default_composition_mode', 'forward')
+        context['default_composition_mode'] = 'comment'
+        res = super(crm_lead_forward_to_partner, self).default_get(cr, uid, fields, context=context)
+        # back to forward mode
+        context['default_composition_mode'] = old_mode
+        res['composition_mode'] = context['default_composition_mode']
+        return res
+
+    def _get_composition_mode_selection(self, cr, uid, context=None):
+        composition_mode = super(crm_lead_forward_to_partner, self)._get_composition_mode_selection(cr, uid, context=context)
+        composition_mode.append(('forward', 'Forward'))
+        return composition_mode
+
     _columns = {
-        'send_to': fields.selection([('user', 'User'), ('partner', 'Partner'), \
-                         ('email', 'Email Address')], 'Send to', required=True),
-        'user_id': fields.many2one('res.users', "User"),
-        'attachment_ids': fields.many2many('ir.attachment','lead_forward_to_partner_attachment_rel', 'wizard_id', 'attachment_id', 'Attachments'),
-        'partner_id' : fields.many2one('res.partner', 'Partner'),
-        'history': fields.selection([('info', 'Case Information'), ('latest', 'Latest email'), ('whole', 'Whole Story')], 'Send history', required=True),
+        'partner_ids': fields.many2many('res.partner',
+            'lead_forward_to_partner_res_partner_rel',
+            'wizard_id', 'partner_id', 'Additional contacts'),
+        'attachment_ids': fields.many2many('ir.attachment',
+            'lead_forward_to_partner_attachment_rel',
+            'wizard_id', 'attachment_id', 'Attachments'),
+        'history_mode': fields.selection([('info', 'Case Information'),
+            ('latest', 'Latest email'), ('whole', 'Whole Story')],
+            'Send history', required=True),
     }
 
     _defaults = {
-        'send_to' : 'email',
-        'history': 'latest',
-        'email_from': lambda s, cr, uid, c: s.pool.get('res.users').browse(cr, uid, uid, c).email,
+        'history_mode': 'latest',
+        'content_subtype': lambda self,cr, uid, context={}: 'html',
     }
 
-    def on_change_email(self, cr, uid, ids, user, context=None):
-        if not user:
-            return {'value': {'email_to': False}}
-        return {'value': {'email_to': self.pool.get('res.users').browse(cr, uid, uid, context=context).email}}
-
-    def on_change_history(self, cr, uid, ids, history_type, context=None):
-        """Gives message body according to type of history selected
-            * info: Forward the case information
-            * whole: Send the whole history
-            * latest: Send the latest histoy
+    def get_record_data(self, cr, uid, model, res_id, context=None):
+        """ Override of mail.compose.message, to add default values coming
+            form the related lead.
         """
-        #TODO: ids and context are not comming
-        res = {}
-        res_id = context.get('active_id')
-        model = context.get('active_model')
-        lead = self.pool.get(model).browse(cr, uid, res_id, context)
-        body_text = self._get_message_body_text(cr, uid, lead, history_type, context=context)
-        if body_text:
-            res = {'value': {'body_text' : body_text}}
+        res = super(crm_lead_forward_to_partner, self).get_record_data(cr, uid, model, res_id, context=context)
+        if model not in ('crm.lead') or not res_id:
+            return res
+        lead_obj = self.pool.get(model)
+        lead = lead_obj.browse(cr, uid, res_id, context=context)
+        subject = '%s: %s - %s' % (_('Fwd'), _('Lead forward'), lead.name)
+        body = self._get_message_body(cr, uid, lead, 'info', context=context)
+        res.update({
+            'subject': subject,
+            'body': body,
+            })
         return res
-    
-    def on_change_partner(self, cr, uid, ids, partner_id):
-        """This function fills address information based on partner/user selected
-        """
-        if not partner_id:
-            return {'value' : {'email_to' : False}}
-        partner_obj = self.pool.get('res.partner')
-        data = {}
-        partner = partner_obj.browse(cr, uid, [partner_id])
-        user_id = partner and partner[0].user_id or False
-        data.update({'email_from': partner and partner[0].email or "", 
-                     'email_cc' : user_id and user_id.user or '', 
-                     'user_id': user_id and user_id.id or False})
-        return {'value' : data}
+
+    def on_change_history_mode(self, cr, uid, ids, history_mode, model, res_id, context=None):
+        """ Update body when changing history_mode """
+        if model and model == 'crm.lead' and res_id:
+            lead = self.pool.get(model).browse(cr, uid, res_id, context=context)
+            body = self._get_message_body(cr, uid, lead, history_mode, context=context)
+            return {'value': {'body': body}}
+
+    def create(self, cr, uid, values, context=None):
+        """ TDE-HACK: remove 'type' from context, because when viewing an
+            opportunity form view, a default_type is set and propagated
+            to the wizard, that has a not matching type field. """
+        default_type = context.pop('default_type', None)
+        new_id = super(crm_lead_forward_to_partner, self).create(cr, uid, values, context=context)
+        if default_type:
+            context['default_type'] = default_type
+        return new_id
 
     def action_forward(self, cr, uid, ids, context=None):
-        """
-        Forward the lead to a partner
-        """
+        """ Forward the lead to a partner """
         if context is None:
             context = {}
         res = {'type': 'ir.actions.act_window_close'}
-        model = context.get('active_model')
-        if model not in ('crm.lead'):
+        wizard = self.browse(cr, uid, ids[0], context=context)
+        if wizard.model not in ('crm.lead'):
             return res
 
-        this = self.browse(cr, uid, ids[0], context=context)
-        lead = self.pool.get(model)
-        lead_id = context and context.get('active_id', False) or False
-        lead_ids = lead_id and [lead_id] or []
-        mode = context.get('mail.compose.message.mode')
-        if mode == 'mass_mail':
+        lead = self.pool.get(wizard.model)
+        lead_ids = wizard.res_id and [wizard.res_id] or []
+
+        if wizard.composition_mode == 'mass_mail':
             lead_ids = context and context.get('active_ids', []) or []
-            value = self.default_get(cr, uid, ['body_text', 'email_to', 'email_cc', 'subject', 'history'], context=context)
+            value = self.default_get(cr, uid, ['body', 'email_to', 'email_cc', 'subject', 'history_mode'], context=context)
             self.write(cr, uid, ids, value, context=context)
-            context['mail.compose.message.mode'] = mode
 
         self.send_mail(cr, uid, ids, context=context)
-        for case in lead.browse(cr, uid, lead_ids, context=context):
-            if (this.send_to == 'partner' and this.partner_id):
-                lead.assign_partner(cr, uid, [case.id], this.partner_id.id, context=context)
-
-            if this.send_to == 'user':
-                lead.allocate_salesman(cr, uid, [case.id], [this.user_id.id], context=context)
-
-            email_cc = to_email(case.email_cc)
-            email_cc = email_cc and email_cc[0] or ''
-            new_cc = []
-            if email_cc:
-                new_cc.append(email_cc)
-            for to in this.email_to.split(','):
-                email_to = to_email(to)
-                email_to = email_to and email_to[0] or ''
-                if email_to not in new_cc:
-                    new_cc.append(to)
-            update_vals = {'email_cc' : ', '.join(new_cc) }
-            lead.write(cr, uid, [case.id], update_vals, context=context)
+        # for case in lead.browse(cr, uid, lead_ids, context=context):
+            # TODO: WHAT TO DO WITH THAT ?
+            # if (this.send_to == 'partner' and this.partner_id):
+            #     lead.assign_partner(cr, uid, [case.id], this.partner_id.id, context=context)
+
+            # if this.send_to == 'user':
+            #     lead.allocate_salesman(cr, uid, [case.id], [this.user_id.id], context=context)
         return res
 
-    def _get_info_body_text(self, cr, uid, lead, context=None):
+    def _get_info_body(self, cr, uid, lead, context=None):
         field_names = []
         proxy = self.pool.get(lead._name)
         if lead.type == 'opportunity':
             field_names += ['partner_id']
         field_names += [
-           'partner_name' , 'title', 'function', 'street', 'street2',
+            'partner_name' , 'title', 'function', 'street', 'street2',
             'zip', 'city', 'country_id', 'state_id', 'email_from',
             'phone', 'fax', 'mobile', 'categ_id', 'description',
         ]
-        return proxy._mail_body_text(cr, uid, lead, field_names, context=context)
+        return proxy._mail_body(cr, uid, lead, field_names, context=context)
 
-    def _get_message_body_text(self, cr, uid, lead, mode='whole', context=None):
-        """This function gets whole communication history and returns as top posting style
+    def _get_message_body(self, cr, uid, lead, history_mode='whole', context=None):
+        """ This function gets whole communication history and returns as top
+            posting style
+            #1: form a body, based on the lead
+            #2: append to the body the communication history, based on the
+                history_mode parameter
+
+            - info: Forward the case information
+            - latest: Send the latest history
+            - whole: Send the whole history
+
+            :param lead: browse_record on crm.lead
+            :param history_mode: 'whole' or 'latest'
         """
         mail_message = self.pool.get('mail.message')
-        message_ids = []
-        body = self._get_info_body_text(cr, uid, lead, context=context)
-        if mode in ('whole', 'latest'):
-            message_ids = lead.message_ids
-            message_ids = map(lambda x: x.id, filter(lambda x: x.email_from, message_ids))
-            if mode == 'latest' and len(message_ids):
-                message_ids = [message_ids[0]]
-            for message in mail_message.browse(cr, uid, message_ids, context=context):
-                header = '-------- Original Message --------'
-                sender = 'From: %s' %(message.email_from or '')
-                to = 'To: %s' % (message.email_to or '')
-                sentdate = 'Date: %s' % (message.date or '')
-                desc = '\n%s'%(message.body_text)
-                original = [header, sender, to, sentdate, desc, '\n']
-                original = '\n'.join(original)
-                body += original
+        body = self._get_info_body(cr, uid, lead, context=context)
+        if history_mode not in ('whole', 'latest'):
+            return body or ''
+        for message in lead.message_ids:
+            header = '-------- Original Message --------'
+            sentdate = 'Date: %s' % (message.date or '')
+            desc = '\n%s'%(message.body)
+            original = [header, sentdate, desc, '\n']
+            original = '\n'.join(original)
+            body += original
+            if history_mode == 'latest':
+                break
         return body or ''
 
-    def get_value(self, cr, uid, model, res_id, context=None):
-        if context is None:
-            context = {}
-        res = super(crm_lead_forward_to_partner, self).get_value(cr, uid,  model, res_id, context=context)
-        if model not in ("crm.lead"):
-            return res
-        proxy = self.pool.get(model)
-        partner = self.pool.get('res.partner')
-        lead = proxy.browse(cr, uid, res_id, context=context)
-        mode = context.get('mail.compose.message.mode')
-        if mode == "forward":
-            body_type = context.get('mail.compose.message.body')
-            email_cc = res.get('email_cc', "")
-            email = res.get('email_to', "")
-            subject = '%s: %s - %s' % (_('Fwd'), 'Lead forward', lead.name)
-            body = self._get_message_body_text(cr, uid, lead, body_type, context=context)
-            partner_assigned_id = lead.partner_assigned_id and lead.partner_assigned_id.id or False
-            user_id = False
-            if not partner_assigned_id:
-                partner_assigned_id = proxy.search_geo_partner(cr, uid, [lead.id], context=None).get(lead.id, False)
-            if partner_assigned_id:
-                assigned_partner = partner.browse(cr, uid, partner_assigned_id, context=context)
-                user_id = assigned_partner.user_id and assigned_partner.user_id.id or False
-                email_cc = assigned_partner.user_id and assigned_partner.user_id.email or ''
-                email = assigned_partner.email
-            
-            res.update({
-                'subject' : subject,
-                'body_text' : body,
-                'email_cc' : email_cc,
-                'email_to' : email,
-                'partner_assigned_id': partner_assigned_id,
-                'user_id': user_id,
-            })
-        return res
-        
 
-    def default_get(self, cr, uid, fields, context=None):
-        if context is None:
-            context = {}
-        context['mail.compose.message.mode'] = 'forward'
-        context['mail.compose.message.body'] = 'info'
-        return super(crm_lead_forward_to_partner, self).default_get(cr, uid, fields, context=context)
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
index cecf739..0d8f27e 100644 (file)
@@ -7,42 +7,31 @@
             <field name="model">crm.lead.forward.to.partner</field>
             <field name="arch" type="xml">
                 <form string="Send Mail" version="7.0">
+                    <field name="composition_mode" colspan="2" nolabel="1" invisible="1"/>
+                    <field name="model" colspan="2" nolabel="1" invisible="1"/>
+                    <field name="res_id" colspan="2" nolabel="1" invisible="1"/>
                     <separator string="Forward to Partner" colspan="4"/>
-                    <group col="4" colspan="6">
-                        <field name="history" colspan="2" on_change="on_change_history(history, context)"/>
-                        <field name="send_to" colspan="2"/>
-                        <group col="2" colspan="2" attrs="{ 'invisible' : [('send_to','!=','user')]}">
-                            <field name="user_id"
-                                attrs="{ 'required' : [('send_to','=','user')]}"
-                                on_change="on_change_email(user_id)"/>
-                        </group>
-                        <group col="4" colspan="4" attrs="{'invisible' : [('send_to','!=','partner')]}">
-                            <field name="partner_id" attrs="{'required' : [('send_to','=','partner')]}" on_change="on_change_partner(partner_id)" colspan="2"/>
-                        </group>
+                    <group col="4">
+                        <field name="history_mode" colspan="4"
+                            on_change="on_change_history_mode(history_mode, model, res_id)"/>
+                        <field name="subject" colspan="4"/>
+                        <field name="partner_ids" colspan="4" widget="many2many_tags"
+                            on_change="onchange_partner_ids(partner_ids)"/>
+                        <notebook colspan="4">
+                            <page string="Body">
+                                <field name="body"/>
+                            </page>
+                            <page string="Attachments">
+                                <field name="attachment_ids"/>
+                            </page>
+                        </notebook>
                     </group>
-                    <separator string="" colspan="4"/>
-                    <group col="6" colspan="4">
-                        <field name="email_from" colspan="4" required="1"/>
-                        <field name="email_to" colspan="4" required="1"/>
-                        <field name="email_cc" colspan="4"/>
-                        <field name="email_bcc" colspan="4"/>
-                        <field name="reply_to" colspan="4"/>
-                        <field name="subject" colspan="4" widget="char" size="512"/>
-                    </group>
-                    <separator string="" colspan="4"/>
-                    <notebook colspan="4">
-                        <page string="Body">
-                            <field name="body_text" colspan="4" nolabel="1"/>
-                        </page>
-                        <page string="Attachments">
-                            <label string="Add here all attachments of the current document you want to include in the Email." colspan="4"/>
-                            <field name="attachment_ids" colspan="4" nolabel="1"/>
-                        </page>
-                    </notebook>
                     <footer>
-                        <button name="action_forward" string="Send" type="object" class="oe_highlight"/>
+                        <button name="action_forward" string="Send" type="object"
+                            class="oe_highlight"/>
                         or
-                        <button string="Cancel" class="oe_link" special="cancel"/>
+                        <button string="Cancel" special="cancel"
+                            class="oe_link"/>
                     </footer>
                 </form>
             </field>
             <field name="target">new</field>
         </record>
 
-
-
         <act_window id="action_crm_send_mass_forward"
             multi="True"
             key2="client_action_multi" name="Mass forward to partner"
             res_model="crm.lead.forward.to.partner" src_model="crm.lead"
             view_mode="form" target="new" view_type="form"
-            context="{'mail.compose.message.mode' : 'mass_mail'}"
+            context="{'default_composition_mode' : 'mass_mail'}"
             view_id="crm_lead_forward_to_partner_form"
-       />
-
-
+        />
 
     </data>
 </openerp>
index b43b7d1..4080e62 100644 (file)
@@ -28,6 +28,8 @@ from osv import osv
 from osv import fields
 import tools
 from tools.translate import _
+from tools.html_sanitize import html_sanitize
+from tools import append_content_to_html
 from urllib import quote as quote
 _logger = logging.getLogger(__name__)
 
@@ -38,10 +40,8 @@ except ImportError:
 
 class email_template(osv.osv):
     "Templates for sending email"
-    _inherit = 'mail.message'
     _name = "email.template"
     _description = 'Email Templates'
-    _rec_name = 'name' # override mail.message's behavior
 
     def render_template(self, cr, uid, template, model, res_id, context=None):
         """Render the given template text, replace mako expressions ``${expr}``
@@ -99,66 +99,46 @@ class email_template(osv.osv):
         mod_name = False
         if model_id:
             mod_name = self.pool.get('ir.model').browse(cr, uid, model_id, context).model
-        return {'value':{'model': mod_name}}
-
-    def name_get(self, cr, uid, ids, context=None):
-        """ Override name_get of mail.message: return directly the template
-            name, and not the generated name from mail.message.common."""
-        return [(record.id, record.name) for record in self.browse(cr, uid, ids, context=context)]
+        return {'value': {'model': mod_name}}
 
     _columns = {
-        'name': fields.char('Name', size=250),
-        'model_id': fields.many2one('ir.model', 'Related document model'),
-        'lang': fields.char('Language Selection', size=250,
+        'name': fields.char('Name'),
+        'model_id': fields.many2one('ir.model', 'Applies to', help="The kind of document with with this template can be used"),
+        'model': fields.related('model_id', 'model', type='char', string='Related Document Model',
+                                size=128, select=True, store=True, readonly=True),
+        'lang': fields.char('Language',
                             help="Optional translation language (ISO code) to select when sending out an email. "
                                  "If not set, the english version will be used. "
                                  "This should usually be a placeholder expression "
                                  "that provides the appropriate language code, e.g. "
-                                 "${object.partner_id.lang.code}."),
+                                 "${object.partner_id.lang.code}.",
+                            placeholder="${object.partner_id.lang.code}"),
         'user_signature': fields.boolean('Add Signature',
                                          help="If checked, the user's signature will be appended to the text version "
                                               "of the message"),
-        'report_name': fields.char('Report Filename', size=200, translate=True,
+        'subject': fields.char('Subject', translate=True, help="Subject (placeholders may be used here)",),
+        'email_from': fields.char('From', help="Sender address (placeholders may be used here)"),
+        'email_to': fields.char('To', help="Comma-separated recipient addresses (placeholders may be used here)"),
+        'email_cc': fields.char('Cc', help="Carbon copy recipients (placeholders may be used here)"),
+        'reply_to': fields.char('Reply-To', help="Preferred response address (placeholders may be used here)"),
+        'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing Mail Server', readonly=False,
+                                          help="Optional preferred server for outgoing mails. If not set, the highest "
+                                               "priority one will be used."),
+        'body_html': fields.text('Body', translate=True, help="Rich-text/HTML version of the message (placeholders may be used here)"),
+        'report_name': fields.char('Report Filename', translate=True,
                                    help="Name to use for the generated report file (may contain placeholders)\n"
                                         "The extension can be omitted and will then come from the report type."),
-        'report_template':fields.many2one('ir.actions.report.xml', 'Optional report to print and attach'),
-        'ref_ir_act_window':fields.many2one('ir.actions.act_window', 'Sidebar action', readonly=True,
+        'report_template': fields.many2one('ir.actions.report.xml', 'Optional report to print and attach'),
+        'ref_ir_act_window': fields.many2one('ir.actions.act_window', 'Sidebar action', readonly=True,
                                             help="Sidebar action to make this template available on records "
                                                  "of the related document model"),
-        'ref_ir_value':fields.many2one('ir.values', 'Sidebar Button', readonly=True,
+        'ref_ir_value': fields.many2one('ir.values', 'Sidebar Button', readonly=True,
                                        help="Sidebar button to open the sidebar action"),
-        'track_campaign_item': fields.boolean('Resource Tracking',
-                                              help="Enable this is you wish to include a special tracking marker "
-                                                   "in outgoing emails so you can identify replies and link "
-                                                   "them back to the corresponding resource record. "
-                                                   "This is useful for CRM leads for example"),
-
-        # Overridden mail.message.common fields for technical reasons:
-        'model': fields.related('model_id','model', type='char', string='Related Document Model',
-                                size=128, select=True, store=True, readonly=True),
-        # we need a separate m2m table to avoid ID collisions with the original mail.message entries
         'attachment_ids': fields.many2many('ir.attachment', 'email_template_attachment_rel', 'email_template_id',
-                                           'attachment_id', 'Files to attach',
+                                           'attachment_id', 'Attachments',
                                            help="You may attach files to this template, to be added to all "
                                                 "emails created from this template"),
-
-        # Overridden mail.message.common fields to make tooltips more appropriate:
-        'subject':fields.char('Subject', size=512, translate=True, help="Subject (placeholders may be used here)",),
-        'email_from': fields.char('From', size=128, help="Sender address (placeholders may be used here)"),
-        'email_to': fields.char('To', size=256, help="Comma-separated recipient addresses (placeholders may be used here)"),
-        'email_cc': fields.char('Cc', size=256, help="Carbon copy recipients (placeholders may be used here)"),
-        'email_bcc': fields.char('Bcc', size=256, help="Blind carbon copy recipients (placeholders may be used here)"),
-        'reply_to': fields.char('Reply-To', size=250, help="Preferred response address (placeholders may be used here)"),
-        'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing Mail Server', readonly=False,
-                                          help="Optional preferred server for outgoing mails. If not set, the highest "
-                                               "priority one will be used."),
-        'body_text': fields.text('Text Contents', translate=True, help="Plaintext version of the message (placeholders may be used here)"),
-        'body_html': fields.text('Rich-text Contents', translate=True, help="Rich-text/HTML version of the message (placeholders may be used here)"),
-        'message_id': fields.char('Message-Id', size=256, help="Message-ID SMTP header to use in outgoing messages based on this template. "
-                                                               "Please note that this overrides the 'Resource Tracking' option, "
-                                                               "so if you simply need to track replies to outgoing emails, enable "
-                                                               "that option instead.\n"
-                                                               "Placeholders must be used here, as this value always needs to be unique!"),
+        'auto_delete': fields.boolean('Auto Delete', help="Permanently delete this email after sending it, to save space"),
 
         # Fake fields used to implement the placeholder assistant
         'model_object_field': fields.many2one('ir.model.fields', string="Field",
@@ -172,12 +152,12 @@ class email_template(osv.osv):
                                                   help="When a relationship field is selected as first field, "
                                                        "this field lets you select the target field within the "
                                                        "destination document model (sub-model)."),
-        'null_value': fields.char('Null value', help="Optional value to use if the target field is empty", size=128),
-        'copyvalue': fields.char('Expression', size=256, help="Final placeholder expression, to be copy-pasted in the desired template field."),
+        'null_value': fields.char('Default Value', help="Optional value to use if the target field is empty"),
+        'copyvalue': fields.char('Placeholder Expression', help="Final placeholder expression, to be copy-pasted in the desired template field."),
     }
 
     _defaults = {
-        'track_campaign_item': True
+        'auto_delete': True,
     }
 
     def create_action(self, cr, uid, ids, context=None):
@@ -195,7 +175,7 @@ class email_template(osv.osv):
                  'res_model': 'mail.compose.message',
                  'src_model': src_obj,
                  'view_type': 'form',
-                 'context': "{'mail.compose.message.mode':'mass_mail', 'mail.compose.template_id' : %d}" % (template.id),
+                 'context': "{'default_composition_mode': 'mass_mail', 'default_template_id' : %d}" % (template.id),
                  'view_mode':'form,tree',
                  'view_id': res_id,
                  'target': 'new',
@@ -222,7 +202,7 @@ class email_template(osv.osv):
                 if template.ref_ir_value:
                     ir_values_obj = self.pool.get('ir.values')
                     ir_values_obj.unlink(cr, uid, template.ref_ir_value.id, context)
-            except:
+            except Exception:
                 raise osv.except_osv(_("Warning"), _("Deletion of the action record failed."))
         return True
 
@@ -294,65 +274,33 @@ class email_template(osv.osv):
            :param res_id: id of the record to use for rendering the template (model
                           is taken from template definition)
            :returns: a dict containing all relevant fields for creating a new
-                     mail.message entry, with the addition one additional
-                     special key ``attachments`` containing a list of
+                     mail.mail entry, with one extra key ``attachments``, in the
+                     format expected by :py:meth:`mail_thread.message_post`.
         """
         if context is None:
             context = {}
-        values = {
-                  'subject': False,
-                  'body_text': False,
-                  'body_html': False,
-                  'email_from': False,
-                  'email_to': False,
-                  'email_cc': False,
-                  'email_bcc': False,
-                  'reply_to': False,
-                  'auto_delete': False,
-                  'model': False,
-                  'res_id': False,
-                  'mail_server_id': False,
-                  'attachments': False,
-                  'attachment_ids': False,
-                  'message_id': False,
-                  'state': 'outgoing',
-                  'content_subtype': 'plain',
-                  'partner_ids': [],
-        }
-        if not template_id:
-            return values
-
         report_xml_pool = self.pool.get('ir.actions.report.xml')
         template = self.get_email_template(cr, uid, template_id, res_id, context)
-
-        for field in ['subject', 'body_text', 'body_html', 'email_from',
-                      'email_to', 'email_cc', 'email_bcc', 'reply_to',
-                      'message_id']:
+        values = {}
+        for field in ['subject', 'body_html', 'email_from',
+                      'email_to', 'email_cc', 'reply_to']:
             values[field] = self.render_template(cr, uid, getattr(template, field),
                                                  template.model, res_id, context=context) \
                                                  or False
-
-        # if email_to: find or create a partner
-        if values['email_to']:
-            partner_id = self.pool.get('mail.thread').message_partner_by_email(cr, uid, values['email_to'], context=context)['partner_id']
-            if not partner_id:
-                partner_id = self.pool.get('res.partner').name_create(cr, uid, values['email_to'], context=context)
-            values['partner_ids'] = [partner_id]
-
-        if values['body_html']:
-            values.update(content_subtype='html')
-
         if template.user_signature:
             signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
-            values['body_text'] += '\n\n' + signature
+            values['body_html'] = append_content_to_html(values['body_html'], signature)
+
+        if values['body_html']:
+            values['body'] = html_sanitize(values['body_html'])
 
-        values.update(mail_server_id = template.mail_server_id.id or False,
-                      auto_delete = template.auto_delete,
+        values.update(mail_server_id=template.mail_server_id.id or False,
+                      auto_delete=template.auto_delete,
                       model=template.model,
                       res_id=res_id or False)
 
-        attachments = {}
-        # Add report as a Document
+        attachments = []
+        # Add report in attachments
         if template.report_template:
             report_name = self.render_template(cr, uid, template.report_name, template.model, res_id, context=context)
             report_service = 'report.' + report_xml_pool.browse(cr, uid, template.report_template.id, context).report_name
@@ -368,12 +316,11 @@ class email_template(osv.osv):
             ext = "." + format
             if not report_name.endswith(ext):
                 report_name += ext
-            attachments[report_name] = result
+            attachments.append(report_name, result)
 
-        # Add document attachments
+        # Add template attachments
         for attach in template.attachment_ids:
-            # keep the bytes as fetched from the db, base64 encoded
-            attachments[attach.datas_fname] = attach.datas
+            attachments.append((attach.datas_fname, attach.datas))
 
         values['attachments'] = attachments
         return values
@@ -391,12 +338,12 @@ class email_template(osv.osv):
            :returns: id of the mail.message that was created 
         """
         if context is None: context = {}
-        mail_message = self.pool.get('mail.message')
+        mail_mail = self.pool.get('mail.mail')
         ir_attachment = self.pool.get('ir.attachment')
         values = self.generate_email(cr, uid, template_id, res_id, context=context)
         assert 'email_from' in values, 'email_from is missing or empty after template rendering, send_mail() cannot proceed'
         attachments = values.pop('attachments') or {}
-        msg_id = mail_message.create(cr, uid, values, context=context)
+        msg_id = mail_mail.create(cr, uid, values, context=context)
         # link attachments
         attachment_ids = []
         for fname, fcontent in attachments.iteritems():
@@ -404,15 +351,15 @@ class email_template(osv.osv):
                     'name': fname,
                     'datas_fname': fname,
                     'datas': fcontent,
-                    'res_model': mail_message._name,
+                    'res_model': mail_mail._name,
                     'res_id': msg_id,
             }
             context.pop('default_type', None)
             attachment_ids.append(ir_attachment.create(cr, uid, attachment_data, context=context))
         if attachment_ids:
-            mail_message.write(cr, uid, msg_id, {'attachment_ids': [(6, 0, attachment_ids)]}, context=context)
+            mail_mail.write(cr, uid, msg_id, {'attachment_ids': [(6, 0, attachment_ids)]}, context=context)
         if force_send:
-            mail_message.send(cr, uid, [msg_id], context=context)
+            mail_mail.send(cr, uid, [msg_id], context=context)
         return msg_id
 
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
index a04790d..448655a 100644 (file)
@@ -1,99 +1,67 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <openerp>
     <data>
-
         <record model="ir.ui.view" id="email_template_form">
             <field name="name">email.template.form</field>
             <field name="model">email.template</field>
             <field name="arch" type="xml">
                 <form string="Templates" version="7.0">
-                  <sheet>
-                    <group col="4">
-                        <field name="name" required="1"/>
-                        <field name="model_id" required="1" on_change="onchange_model_id(model_id)"/>
-                        <field name="model" invisible="1"/>
-                    </group>
-                    <notebook>
-                        <page string="Email Details">
-                            <group>
-                                <field name="email_from" required="1"/>
-                                <field name="email_to" required="1"/>
-                                <field name="email_cc"/>
-                                <field name="email_bcc"/>
-                                <field name="reply_to"/>
-                                <field name="subject" required="1"/>
-                                <notebook  colspan="4">
-                                    <page string="Body (Text)">
-                                        <field name="body_text" colspan="4" width="250" height="250" nolabel="1"/>
-                                    </page>
-                                    <page string="Body (Rich/HTML)">
-                                        <field name="body_html" colspan="4" width="250" height="250" nolabel="1"/>
-                                        <label string="Note: This is Raw HTML." colspan="4"/>
-                                    </page>
-                                </notebook>
-                                <field name="user_signature"/>
-                            <notebook colspan="4">
-                                <page string="Dynamic Values Builder">
-                                    <group>
-                                        <field name="model_object_field"
-                                            domain="[('model_id','=',model_id),('ttype','!=','one2many'),('ttype','!=','many2many')]"
-                                            on_change="onchange_sub_model_object_value_field(model_object_field)"/>
-                                        <field name="sub_object" readonly="1"/>
-                                        <field name="sub_model_object_field"
-                                            domain="[('model_id','=',sub_object),('ttype','!=','one2many'),('ttype','!=','many2many')]"
-                                            attrs="{'readonly':[('sub_object','=',False)],'required':[('sub_object','!=',False)]}"
-                                            on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field)"/>
-                                        <field name="null_value" on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field,null_value)"/>
-                                        <field name="copyvalue"/>
+                    <sheet>
+                        <div class="oe_title">
+                            <label for="name" class="oe_edit_only"/><h1><field name="name" required="1"/></h1>
+                            <h3><label for="model_id"/><field name="model_id" required="1" on_change="onchange_model_id(model_id)" class="oe_inline"/></h3>
+                            <field name="model" invisible="1"/>
+                        </div>
+                        <div class="oe_right oe_button_box" name="buttons">
+                            <field name="ref_ir_act_window" invisible="1"/>
+                            <button name="create_action" string="Add context action" type="object"
+                                    attrs="{'invisible':[('ref_ir_act_window','!=',False)]}"
+                                    help="Display an option on related documents to open a composition wizard with this template"/>
+                            <button name="unlink_action" string="Remove context action" type="object" 
+                                    attrs="{'invisible':[('ref_ir_act_window','=',False)]}"
+                                    help="Remove the contextual action to use this template on related documents"/>
+                            <button name="%(wizard_email_template_preview)d" string="Preview"
+                                    type="action" target="new"
+                                    context="{'template_id':active_id}"/>
+                        </div>
+                        <notebook>
+                            <page string="Email Details">
+                                <group>
+                                    <group string="Addressing">
+                                      <field name="email_from" required="1"/>
+                                      <field name="email_to" required="1"/>
+                                      <field name="email_cc"/>
+                                      <field name="reply_to"/>
+                                      <field name="user_signature"/>
+                                    </group>
+                                    <group string="Dynamic Value Builder" class="oe_edit_only">
+                                            <field name="model_object_field" domain="[('model_id','=',model_id),('ttype','!=','one2many'),('ttype','!=','many2many')]" on_change="onchange_sub_model_object_value_field(model_object_field)"/>
+                                            <field name="sub_object" readonly="1"/>
+                                            <field name="sub_model_object_field" domain="[('model_id','=',sub_object),('ttype','!=','one2many'),('ttype','!=','many2many')]" attrs="{'readonly':[('sub_object','=',False)],'required':[('sub_object','!=',False)]}" on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field)"/>
+                                            <field name="null_value" on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field,null_value)"/>
+                                            <field name="copyvalue"/>
+                                    </group>
+                                    <group string="Contents" colspan="2">
+                                       <field name="subject" required="1"/>
+                                       <field name="body_html" width="250" height="450" nolabel="1" colspan="2" placeholder="Email contents (in raw HTML format)"/>
                                     </group>
-                                </page>
-                            </notebook>
-                            <button name="%(wizard_email_template_preview)d" string="Preview Template"
-                                type="action" colspan="4" target="new" icon="gtk-zoom-fit" context="{'template_id':active_id}"/>
-                            </group>
-                        </page>
-                        <page string="Advanced">
-                            <group colspan="2" col="2">
-                                <group colspan="2" col="2">
-                                    <separator string="Sidebar Button" colspan="2"/>
-                                    <button name="create_action" string="Add sidebar button" type="object" icon="gtk-execute"
-                                            colspan="2" attrs="{'invisible':[('ref_ir_act_window','!=',False)]}"
-                                            help="Display a button in the sidebar of related documents to open a composition wizard with this template"
-                                            />
-                                    <field name="ref_ir_act_window" attrs="{'invisible':[('ref_ir_act_window','=',False)]}"/>
-                                    <field name="ref_ir_value" attrs="{'invisible':[('ref_ir_act_window','=',False)]}"/>
-                                    <button name="unlink_action" string="Remove sidebar button" type="object" icon="gtk-delete"
-                                            colspan="2" attrs="{'invisible':[('ref_ir_act_window','=',False)]}"
-                                            help="Remove the sidebar button currently displayed on related documents"
-                                            />
                                 </group>
-                                <group colspan="2" col="2">
-                                    <separator string="Advanced Options" colspan="2"/>
+                            </page>
+                            <page string="Advanced">
+                                <group>
+                                    <field name="lang"/>
                                     <field name="mail_server_id"/>
-                                    <field name="track_campaign_item"/>
-                                    <field name="message_id"/>
                                     <field name="auto_delete"/>
-                                    <field name="lang"/>
+                                    <field name="report_template" domain="[('model','=',model)]"/>
+                                    <field name="report_name" class="oe_inline"
+                                           attrs="{'invisible':[('report_template','=',False)]}"/>
+                                    <field name="attachment_ids">
+                                       <tree><field name="name"/></tree>
+                                    </field>
                                 </group>
-                            </group>
-                            <group colspan="2" col="2">
-                                <separator string="Attachments" colspan="2"/>
-                                <notebook colspan="2">
-                                    <page string="Attach Report">
-                                        <group>
-                                            <field name="report_template" colspan="4"
-                                                domain="[('model','=',model)]"/>
-                                            <field name="report_name" colspan="4" />
-                                        </group>
-                                    </page>
-                                    <page string="Attach existing files">
-                                        <field name="attachment_ids" colspan="4" nolabel="1" height="350"/>
-                                    </page>
-                                </notebook>
-                            </group>
-                        </page>
-                    </notebook>
-                   </sheet>
+                            </page>
+                        </notebook>
+                       </sheet>
                 </form>
             </field>
         </record>
             <field name="model">email.template</field>
             <field name="arch" type="xml">
                 <tree string="Templates">
-                    <field name="model_id"/>
                     <field name="mail_server_id" invisible="1"/>
                     <field name="name"/>
+                    <field name="model_id"/>
                     <field name="subject"/>
                     <field name="email_from"/>
                     <field name="email_to"/>
index 0990a94..519755a 100644 (file)
@@ -289,8 +289,8 @@ msgid "Add Signature"
 msgstr ""
 
 #. module: email_template
-#: help:email.template,body_text:0
-#: help:email_template.preview,body_text:0
+#: help:email.template,body:0
+#: help:email_template.preview,body:0
 msgid "Plaintext version of the message (placeholders may be used here)"
 msgstr ""
 
@@ -425,8 +425,8 @@ msgid "Cc"
 msgstr ""
 
 #. module: email_template
-#: field:email.template,body_text:0
-#: field:email_template.preview,body_text:0
+#: field:email.template,body:0
+#: field:email_template.preview,body:0
 msgid "Text Contents"
 msgstr ""
 
index d2d6adf..adce042 100644 (file)
@@ -1,5 +1,3 @@
 id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink\r
-access_email_template,email.template,model_email_template,,1,0,0,0\r
+access_email_template,email.template,model_email_template,,1,1,1,0\r
 access_email_template_system,email.template system,model_email_template,base.group_system,1,1,1,1\r
-access_email_template_manager,email.template,model_email_template,,1,1,1,1\r
-access_email_template_preview_system,email.template.preview system,model_email_template_preview,base.group_system,1,1,1,1\r
diff --git a/addons/email_template/tests/__init__.py b/addons/email_template/tests/__init__.py
new file mode 100644 (file)
index 0000000..d63c563
--- /dev/null
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Business Applications
+#    Copyright (c) 2012-TODAY 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/>.
+#
+##############################################################################
+from . import test_mail
+
+checks = [
+    test_mail,
+]
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
\ No newline at end of file
diff --git a/addons/email_template/tests/test_mail.py b/addons/email_template/tests/test_mail.py
new file mode 100644 (file)
index 0000000..85ab3af
--- /dev/null
@@ -0,0 +1,186 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Business Applications
+#    Copyright (c) 2012-TODAY 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 base64
+from openerp.tests import common
+
+class test_message_compose(common.TransactionCase):
+
+    def _mock_smtp_gateway(self, *args, **kwargs):
+        return True
+
+    def _mock_build_email(self, *args, **kwargs):
+        self._build_email_args = args
+        self._build_email_kwargs = kwargs
+        return self.build_email_real(*args, **kwargs)
+
+    def setUp(self):
+        super(test_message_compose, self).setUp()
+        self.mail_group = self.registry('mail.group')
+        self.mail_mail = self.registry('mail.mail')
+        self.mail_message = self.registry('mail.message')
+        self.res_users = self.registry('res.users')
+        self.res_partner = self.registry('res.partner')
+
+        # Install mock SMTP gateway
+        self.build_email_real = self.registry('ir.mail_server').build_email
+        self.registry('ir.mail_server').build_email = self._mock_build_email
+        self.registry('ir.mail_server').send_email = self._mock_smtp_gateway
+
+        # create a 'pigs' and 'bird' groups that will be used through the various tests
+        self.group_pigs_id = self.mail_group.create(self.cr, self.uid,
+            {'name': 'Pigs', 'description': 'Fans of Pigs, unite !'})
+        self.group_bird_id = self.mail_group.create(self.cr, self.uid,
+            {'name': 'Bird', 'description': 'I am angry !'})
+
+    def test_00_message_compose_wizard(self):
+        """ Tests designed for the mail.compose.message wizard updated by email_template. """
+        cr, uid = self.cr, self.uid
+        mail_compose = self.registry('mail.compose.message')
+        self.res_users.write(cr, uid, [uid], {'signature': 'Admin', 'email': 'a@a.a'})
+        user_admin = self.res_users.browse(cr, uid, uid)
+        p_a_id = user_admin.partner_id.id
+        group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
+        group_bird = self.mail_group.browse(cr, uid, self.group_bird_id)
+
+        # Mail data
+        _subject1 = 'Pigs'
+        _subject2 = 'Bird'
+        _body_html1 = 'Fans of Pigs, unite !\n<pre>Admin</pre>\n'
+        _body_html2 = 'I am angry !\n<pre>Admin</pre>\n'
+        _attachments = [
+            {'name': 'First', 'datas_fname': 'first.txt', 'datas': base64.b64encode('My first attachment')},
+            {'name': 'Second', 'datas_fname': 'second.txt', 'datas': base64.b64encode('My second attachment')}
+            ]
+        _attachments_test = [('first.txt', 'My first attachment'), ('second.txt', 'My second attachment')]
+
+        # Create template on mail.group, with attachments
+        group_model_id = self.registry('ir.model').search(cr, uid, [('model', '=', 'mail.group')])[0]
+        email_template = self.registry('email.template')
+        email_template_id = email_template.create(cr, uid, {'model_id': group_model_id,
+            'name': 'Pigs Template', 'subject': '${object.name}',
+            'body_html': '${object.description}', 'user_signature': True,
+            'attachment_ids': [(0, 0, _attachments[0]), (0, 0, _attachments[1])],
+            'email_to': 'b@b.b c@c.c', 'email_cc': 'd@d.d'})
+
+        # ----------------------------------------
+        # CASE1: comment and save as template
+        # ----------------------------------------
+
+        # 1. Comment on pigs
+        compose_id = mail_compose.create(cr, uid,
+            {'subject': 'Forget me subject', 'body': '<p>Dummy body</p>'},
+            {'default_composition_mode': 'comment', 'default_model': 'mail.group',
+                'default_res_id': self.group_pigs_id,
+                'default_template_id': email_template_id,
+                'active_ids': [self.group_pigs_id, self.group_bird_id]})
+        compose = mail_compose.browse(cr, uid, compose_id)
+
+        # 2. Save current composition form as a template
+        mail_compose.save_as_template(cr, uid, [compose_id], context={'default_model': 'mail.group'})
+        # Test: email_template subject, body_html, model
+        last_template_id = email_template.search(cr, uid, [('model', '=', 'mail.group'), ('subject', '=', 'Forget me subject')], limit=1)[0]
+        self.assertTrue(last_template_id, 'email_template not found for model mail.group, subject Forget me subject')
+        last_template = email_template.browse(cr, uid, last_template_id)
+        self.assertEqual(last_template.body_html, '<p>Dummy body</p>', 'email_template incorrect body_html')
+
+        # ----------------------------------------
+        # CASE2: comment with template, save as template
+        # ----------------------------------------
+
+        # 1. Comment on pigs
+        compose_id = mail_compose.create(cr, uid,
+            {'subject': 'Forget me subject', 'body': 'Dummy body'},
+            {'default_composition_mode': 'comment', 'default_model': 'mail.group',
+                'default_res_id': self.group_pigs_id,
+                'default_template_id': email_template_id,
+                'active_ids': [self.group_pigs_id, self.group_bird_id]})
+        compose = mail_compose.browse(cr, uid, compose_id)
+
+        # 2. Perform 'toggle_template', to set use_template and use template_id
+        mail_compose.toggle_template(cr, uid, [compose_id], {'default_composition_mode': 'comment', 'default_model': 'mail.group'})
+        compose.refresh()
+        message_pids = [partner.id for partner in compose.partner_ids]
+        partner_ids = self.res_partner.search(cr, uid, [('email', 'in', ['b@b.b', 'c@c.c', 'd@d.d'])])
+        # Test: mail.compose.message: subject, body, content_subtype, partner_ids
+        self.assertEqual(compose.subject, _subject1, 'mail.compose.message subject incorrect')
+        self.assertEqual(compose.body, _body_html1, 'mail.compose.message body incorrect')
+        self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message content_subtype incorrect')
+        self.assertEqual(set(message_pids), set(partner_ids), 'mail.compose.message partner_ids incorrect')
+        # Test: mail.compose.message: attachments
+        # Test: mail.message: attachments
+        for attach in compose.attachment_ids:
+            self.assertEqual(attach.res_model, 'mail.group', 'mail.message attachment res_model incorrect')
+            self.assertEqual(attach.res_id, self.group_pigs_id, 'mail.message attachment res_id incorrect')
+            self.assertIn((attach.name, base64.b64decode(attach.datas)), _attachments_test,
+                'mail.message attachment name / data incorrect')
+
+        # 3. Perform 'toggle_template': template is not set anymore
+        mail_compose.toggle_template(cr, uid, [compose_id], {'default_composition_mode': 'comment', 'default_model': 'mail.group'})
+        compose.refresh()
+        # Test: subject, body, partner_ids
+        self.assertEqual(compose.subject, False, 'mail.compose.message subject incorrect')
+        self.assertEqual(compose.body, '', 'mail.compose.message body incorrect')
+
+        # ----------------------------------------
+        # CASE3: mass_mail with template
+        # ----------------------------------------
+
+        # 1. Mass_mail on pigs and bird, with a default_partner_ids set to check he is correctly added
+        compose_id = mail_compose.create(cr, uid,
+            {'subject': 'Forget me subject', 'body': 'Dummy body'},
+            {'default_composition_mode': 'mass_mail', 'default_model': 'mail.group',
+                'default_res_id': self.group_pigs_id,
+                'default_template_id': email_template_id,
+                'default_partner_ids': [p_a_id],
+                'active_ids': [self.group_pigs_id, self.group_bird_id]})
+        compose = mail_compose.browse(cr, uid, compose_id)
+
+        # 2. Perform 'toggle_template', to set use_template and use template_id
+        mail_compose.toggle_template(cr, uid, [compose_id], {'default_composition_mode': 'comment', 'default_model': 'mail.group'})
+        compose.refresh()
+        message_pids = [partner.id for partner in compose.partner_ids]
+        partner_ids = [p_a_id]
+        # Test: mail.compose.message: subject, body, content_subtype, partner_ids
+        self.assertEqual(compose.subject, '${object.name}', 'mail.compose.message subject incorrect')
+        self.assertEqual(compose.body, '${object.description}', 'mail.compose.message body incorrect')
+        self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message content_subtype incorrect')
+        self.assertEqual(set(message_pids), set(partner_ids), 'mail.compose.message partner_ids incorrect')
+
+        # 3. Post the comment, get created message
+        mail_compose.send_mail(cr, uid, [compose_id],  {'default_res_id': -1, 'active_ids': [self.group_pigs_id, self.group_bird_id]})
+        group_pigs.refresh()
+        group_bird.refresh()
+        message_pigs = group_pigs.message_ids[0]
+        message_bird = group_bird.message_ids[0]
+        # Test: subject, body
+        self.assertEqual(message_pigs.subject, _subject1, 'mail.message subject on Pigs incorrect')
+        self.assertEqual(message_bird.subject, _subject2, 'mail.message subject on Bird incorrect')
+        self.assertEqual(message_pigs.body, _body_html1, 'mail.message body on Pigs incorrect')
+        self.assertEqual(message_bird.body, _body_html2, 'mail.message body on Bird incorrect')
+        # Test: partner_ids: p_a_id (default) + 3 newly created partners
+        message_pigs_pids = [partner.id for partner in message_pigs.partner_ids]
+        message_bird_pids = [partner.id for partner in message_bird.partner_ids]
+        partner_ids = self.res_partner.search(cr, uid, [('email', 'in', ['b@b.b', 'c@c.c', 'd@d.d'])]) + [p_a_id]
+        self.assertEqual(len(message_pigs_pids), len(partner_ids), 'mail.message on pigs incorrect number of partner_ids')
+        self.assertEqual(len(message_bird_pids), len(partner_ids), 'mail.message on bird partner_ids incorrect')
+        self.assertEqual(set(message_pigs_pids), set(partner_ids), 'mail.message on pigs incorrect number of partner_ids')
+        self.assertEqual(set(message_bird_pids), set(partner_ids), 'mail.message on bird partner_ids incorrect')
index 4ef0e9f..862c34c 100644 (file)
@@ -26,7 +26,6 @@ class email_template_preview(osv.osv_memory):
     _inherit = "email.template"
     _name = "email_template.preview"
     _description = "Email Template Preview"
-    _rec_name = "subject"
 
     def _get_records(self, cr, uid, context=None):
         """
@@ -66,30 +65,19 @@ class email_template_preview(osv.osv_memory):
         return result
 
     _columns = {
-        'res_id':fields.selection(_get_records, 'Sample Document'),
+        'res_id': fields.selection(_get_records, 'Sample Document'),
     }
 
     def on_change_res_id(self, cr, uid, ids, res_id, context=None):
-        if not res_id:
-            return {}
+        if not res_id: return {}
         vals = {}
         email_template = self.pool.get('email.template')
         template_id = context and context.get('template_id')
-        template = email_template.get_email_template(cr, uid, template_id=template_id, record_id=res_id, context=context)
-        model = template.model
-        vals['email_to'] = self.render_template(cr, uid, template.email_to, model, res_id, context)
-        vals['email_cc'] = self.render_template(cr, uid, template.email_cc, model, res_id, context)
-        vals['email_bcc'] = self.render_template(cr, uid, template.email_bcc, model, res_id, context)
-        vals['reply_to'] = self.render_template(cr, uid, template.reply_to, model, res_id, context)
-        vals['subject'] = self.render_template(cr, uid, template.subject, model, res_id, context)
-        description = self.render_template(cr, uid, template.body_text, model, res_id, context) or ''
-        if template.user_signature:
-            signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
-            description += '\n' + signature
-        vals['body_text'] = description
-        if template.body_html:
-            vals['body_html'] = self.render_template(cr, uid, template.body_html, model, res_id, context) or ''
-        vals['report_name'] = self.render_template(cr, uid, template.report_name, model, res_id, context)
+        template = email_template.browse(cr, uid, template_id, context=context)
+        vals['name'] = template.name
+        mail_values = email_template.generate_email(cr, uid, template_id, res_id, context=context)
+        for k in ('email_from','email_to','email_cc','reply_to','subject','body_html'):
+            vals[k] = mail_values[k]
         return {'value': vals}
 
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
index 39fb413..f4f7556 100644 (file)
@@ -6,27 +6,22 @@
             <field name="name">email_template.preview.form</field>
             <field name="model">email_template.preview</field>
             <field name="arch" type="xml">
-                <form string="Email Preview">
-                    <field name="model_id" invisible="1"/>
-                    <field name="res_id" on_change="on_change_res_id(res_id, context)"/>
-                    <group col="2" colspan="4">
-                        <field name="email_to" readonly="1"/>
-                        <field name="email_cc" readonly="1" attrs="{'invisible': [('email_cc','=',False)]}"/>
-                        <field name="email_bcc" readonly="1" attrs="{'invisible': [('email_bcc','=',False)]}"/>
-                        <field name="reply_to" readonly="1" attrs="{'invisible': [('reply_to','=',False)]}"/>
-                        <field name="subject" readonly="1"/>
+                <form string="Email Preview" version="7.0">
+                    <div>
+                        <h2>Preview of <field name="name" readonly="1" class="oe_inline"/></h2>
+                        <field name="model_id" invisible="1"/>
+                        <h3>Using sample document
+                        <field name="res_id" on_change="on_change_res_id(res_id, context)" class="oe_inline"/>
+                        </h3>
+                    </div>
+                    <group>
+                      <field name="email_from" readonly="1"/>
+                      <field name="email_to" readonly="1"/>
+                      <field name="email_cc" readonly="1" attrs="{'invisible':[('email_cc','=',False)]}"/>
+                      <field name="reply_to" readonly="1" attrs="{'invisible':[('reply_to','=',False)]}"/>
+                      <field name="subject" readonly="1"/>
+                      <field name="body_html" widget="html" readonly="1"/>
                     </group>
-                    <group col="4" colspan="4">
-                        <notebook>
-                            <page string="Body (Text)">
-                                <field name="body_text" nolabel="1" colspan="4" height="350" width="350" readonly="1"/>
-                            </page>
-                            <page string="Body (Rich/HTML)" attrs="{'invisible': [('body_html','=', False)]}">
-                                <field name="body_html" nolabel="1" colspan="4" height="350" width="350" readonly="1"/>
-                            </page>
-                        </notebook>
-                    </group>
-                    <field name="report_name" colspan="4" readonly="1"/>
                 </form>
             </field>
         </record>
index 1b007a9..2baef6e 100644 (file)
 #
 ##############################################################################
 
-import base64
+import tools
 
 from osv import osv
 from osv import fields
-from tools.translate import _
-import tools
-
 
 class mail_compose_message(osv.osv_memory):
     _inherit = 'mail.compose.message'
 
     def _get_templates(self, cr, uid, context=None):
-        """
-        Return Email Template of particular  Model.
-        """
         if context is None:
             context = {}
-        record_ids = []
-        email_template= self.pool.get('email.template')
         model = False
-        if context.get('message_id'):
-            mail_message = self.pool.get('mail.message')
-            message_data = mail_message.browse(cr, uid, int(context.get('message_id')), context)
-            model = message_data.model
-        elif context.get('mail.compose.target.model') or context.get('active_model'):
-            model = context.get('mail.compose.target.model', context.get('active_model'))
+        email_template_obj = self.pool.get('email.template')
+        message_id = context.get('default_parent_id', context.get('message_id', context.get('active_id')))
+
+        if context.get('default_composition_mode') == 'reply' and message_id:
+            message_data = self.pool.get('mail.message').browse(cr, uid, message_id, context=context)
+            if message_data:
+                model = message_data.model
+        else:
+            model = context.get('default_model', context.get('active_model'))
+
         if model:
-            record_ids = email_template.search(cr, uid, [('model', '=', model)])
-            return email_template.name_get(cr, uid, record_ids, context) + [(False,'')]
+            record_ids = email_template_obj.search(cr, uid, [('model', '=', model)], context=context)
+            return email_template_obj.name_get(cr, uid, record_ids, context) + [(False, '')]
         return []
 
+    def default_get(self, cr, uid, fields, context=None):
+        if context is None:
+            context = {}
+        result = super(mail_compose_message, self).default_get(cr, uid, fields, context=context)
+        result['template_id'] = context.get('default_template_id', context.get('mail.compose.template_id', False))
+        # force html when using templates
+        if result.get('use_template'):
+            result['content_subtype'] = 'html'
+        return result
+
     _columns = {
         'use_template': fields.boolean('Use Template'),
-        'template_id': fields.selection(_get_templates, 'Template',
-                                        size=-1 # means we want an int db column
-                                        ),
-    }
-    
-    _defaults = {
-        'template_id' : lambda self, cr, uid, context={} : context.get('mail.compose.template_id', False)          
+        # incredible hack of the day: size=-1 means we want an int db column instead of an str one
+        'template_id': fields.selection(_get_templates, 'Template', size=-1),
     }
 
-    def on_change_template(self, cr, uid, ids, use_template, template_id, email_from=None, email_to=None, context=None):
-        if context is None:
-            context = {}
-        values = {}
-        if template_id:
-            res_id = context.get('active_id', False)
-            if context.get('mail.compose.message.mode') == 'mass_mail':
-                # use the original template values - to be rendered when actually sent
-                # by super.send_mail()
-                values = self.pool.get('email.template').read(cr, uid, template_id, self.fields_get_keys(cr, uid), context)
-            else:
-                # render the mail as one-shot
-                values = self.pool.get('email.template').generate_email(cr, uid, template_id, res_id, context=context)
-                # get partner_ids back
-                values['dest_partner_ids'] = values['partner_ids']
-                # retrofit generated attachments in the expected field format
-                if values['attachments']:
-                    attachment = values.pop('attachments')
-                    attachment_obj = self.pool.get('ir.attachment')
-                    att_ids = []
-                    for fname, fcontent in attachment.iteritems():
-                        data_attach = {
-                            'name': fname,
-                            'datas': fcontent,
-                            'datas_fname': fname,
-                            'description': fname,
-                            'res_model' : self._name,
-                            'res_id' : ids[0] if ids else False
-                        }
-                        att_ids.append(attachment_obj.create(cr, uid, data_attach))
-                    values['attachment_ids'] = att_ids
+    def onchange_template_id(self, cr, uid, ids, use_template, template_id, composition_mode, model, res_id, context=None):
+        """ - use_template not set: return default_get
+            - use_template set in mass_mailing: we cannot render, so return the template values
+            - use_template set: return rendered values """
+        if use_template and template_id and composition_mode == 'mass_mail':
+            values = self.pool.get('email.template').read(cr, uid, template_id, ['subject', 'body_html'], context)
+            values.pop('id')
+        elif use_template and template_id:
+            values = self.generate_email_for_composer(cr, uid, template_id, res_id, context=context)
+            # transform attachments into attachment_ids
+            values['attachment_ids'] = []
+            ir_attach_obj = self.pool.get('ir.attachment')
+            for attach_fname, attach_datas in values.pop('attachments', []):
+                data_attach = {
+                    'name': attach_fname,
+                    'datas': attach_datas,
+                    'datas_fname': attach_fname,
+                    'res_model': model,
+                    'res_id': res_id,
+                }
+                values['attachment_ids'].append(ir_attach_obj.create(cr, uid, data_attach, context=context))
         else:
-            # restore defaults
-            values = self.default_get(cr, uid, self.fields_get_keys(cr, uid), context)
-            values.update(use_template=use_template, template_id=template_id)
+            values = self.default_get(cr, uid, ['body', 'body_html', 'subject', 'partner_ids', 'attachment_ids'], context=context)
 
+        if values.get('body_html'):
+            values['body'] = values.pop('body_html')
+        values.update(use_template=use_template, template_id=template_id)
         return {'value': values}
 
-    def template_toggle(self, cr, uid, ids, context=None):
+    def toggle_template(self, cr, uid, ids, context=None):
+        """ hit toggle template mode button: calls onchange_use_template to
+            emulate an on_change, then writes the values to update the form. """
         for record in self.browse(cr, uid, ids, context=context):
-            values = {}
-            use_template = record.use_template
-            # simulate an on_change on use_template
-            values.update(self.onchange_use_template(cr, uid, ids, not use_template, context=context)['value'])
-            record.write(values)
-            return False
-
-    def onchange_use_template(self, cr, uid, ids, use_template, context=None):
-        values = {'use_template': use_template}
-        for record in self.browse(cr, uid, ids, context=context):
-            if not use_template:
-                # equivalent to choosing an empty template
-                onchange_template_values = self.on_change_template(cr, uid, record.id, use_template,
-                    False, email_from=record.email_from, email_to=record.email_to, context=context)
-                values.update(onchange_template_values['value'])
-        return {'value': values}
+            onchange_res = self.onchange_use_template(cr, uid, ids, not record.use_template,
+                record.template_id, record.composition_mode, record.model, record.res_id, context=context).get('value', {})
+            # update partner_ids and attachment_ids
+            onchange_res['partner_ids'] = [(4, partner_id) for partner_id in onchange_res.pop('partner_ids', [])]
+            onchange_res['attachment_ids'] = [(4, attachment_id) for attachment_id in onchange_res.pop('attachment_ids', [])]
+            record.write(onchange_res)
+        return True
+
+    def onchange_use_template(self, cr, uid, ids, use_template, template_id, composition_mode, model, res_id, context=None):
+        """ onchange_use_template (values: True or False).  If use_template is
+            False, we do as an onchange with template_id False for values """
+        values = self.onchange_template_id(cr, uid, ids, use_template,
+            template_id, composition_mode, model, res_id, context=context)
+        # force html when using templates
+        if use_template:
+            values['value']['content_subtype'] = 'html'
+        return values
 
     def save_as_template(self, cr, uid, ids, context=None):
-        if context is None:
-            context = {}
+        """ hit save as template button: current form value will be a new
+            template attached to the current document. """
         email_template = self.pool.get('email.template')
-        model_pool = self.pool.get('ir.model')
+        ir_model_pool = self.pool.get('ir.model')
         for record in self.browse(cr, uid, ids, context=context):
-            model = record.model or context.get('active_model')
-            model_ids = model_pool.search(cr, uid, [('model', '=', model)])
+            model_ids = ir_model_pool.search(cr, uid, [('model', '=', record.model)], context=context)
             model_id = model_ids and model_ids[0] or False
             model_name = ''
             if model_id:
-                model_name = model_pool.browse(cr, uid, model_id, context=context).name
+                model_name = ir_model_pool.browse(cr, uid, model_id, context=context).name
             template_name = "%s: %s" % (model_name, tools.ustr(record.subject))
             values = {
                 'name': template_name,
-                'email_from': record.email_from or False,
                 'subject': record.subject or False,
-                'body_text': record.body_text or False,
-                'email_to': record.email_to or False,
-                'email_cc': record.email_cc or False,
-                'email_bcc': record.email_bcc or False,
-                'reply_to': record.reply_to or False,
+                'body_html': record.body or False,
                 'model_id': model_id or False,
                 'attachment_ids': [(6, 0, [att.id for att in record.attachment_ids])]
             }
             template_id = email_template.create(cr, uid, values, context=context)
-            record.write({'template_id': template_id,
-                          'use_template': True})
+            record.write({'template_id': template_id, 'use_template': True})
+        return True
+
+    #------------------------------------------------------
+    # Wizard validation and send
+    #------------------------------------------------------
 
-        # _reopen same wizard screen with new template preselected
-        return False
+    def generate_email_for_composer(self, cr, uid, template_id, res_id, context=None):
+        """ Call email_template.generate_email(), get fields relevant for
+            mail.compose.message, transform email_cc and email_to into partner_ids """
+        template_values = self.pool.get('email.template').generate_email(cr, uid, template_id, res_id, context=context)
+        # filter template values
+        fields = ['body', 'body_html', 'subject', 'email_to', 'email_cc', 'attachments']
+        values = dict((field, template_values[field]) for field in fields if template_values.get(field))
+        values['body'] = values.pop('body_html', '')
+        # transform email_to, email_cc into partner_ids
+        values['partner_ids'] = []
+        mails = tools.email_split(values.pop('email_to', '') + ' ' + values.pop('email_cc', ''))
+        for mail in mails:
+            partner_id = self.pool.get('res.partner').find_or_create(cr, uid, mail, context=context)
+            values['partner_ids'].append(partner_id)
+        return values
+
+    def render_message(self, cr, uid, wizard, res_id, context=None):
+        """ Generate an email from the template for given (model, res_id) pair.
+            This method is meant to be inherited by email_template that will
+            produce a more complete dictionary, with email_to, ...
+        """
+        # generate the composer email
+        values = self.generate_email_for_composer(cr, uid, wizard.template_id, res_id, context=context)
+        # get values to return
+        email_dict = super(mail_compose_message, self).render_message(cr, uid, wizard, res_id, context)
+        email_dict.update(values)
+        return email_dict
 
-    # override the basic implementation 
     def render_template(self, cr, uid, template, model, res_id, context=None):
         return self.pool.get('email.template').render_template(cr, uid, template, model, res_id, context=context)
 
index 0d4362d..96aaa1c 100644 (file)
                 <data>
                     <xpath expr="//form/footer/button" position="before">
                         <field name="use_template" invisible="1"/>
-                        <button icon="gtk-paste" type="object" name="template_toggle"
+                        <button icon="gtk-paste" type="object" name="toggle_template"
                                 string="" help="Use a message template" />
                         <button icon="gtk-save" type="object" name="save_as_template"
                                 string="" help="Save as a new template"/>
                     </xpath>
                     <xpath expr="//form/notebook" position="after">
                         <group attrs="{'invisible':[('use_template','=',False)]}" colspan="4" col="4">
+                            <field name="use_template" invisible="1"/>
                             <field name="template_id" colspan="3"
-                                   on_change="on_change_template(use_template, template_id, email_from, email_to, context)"/>
+                                on_change="onchange_template_id(use_template, template_id, composition_mode, model, res_id, context)"/>
                         </group>
                     </xpath>
                 </data>
             <field name="inherit_id" ref="mail.email_compose_message_wizard_form_chatter"/>
             <field name="arch" type="xml">
                 <data>
-                    <xpath expr="//field[@name='dest_partner_ids']" position="after">
+                    <xpath expr="//field[@name='partner_ids']" position="after">
                         <field name="use_template" colspan="2" nolabel="1" invisible="1"
-                            on_change="onchange_use_template(use_template, context)"/>
+                            on_change="onchange_use_template(use_template, template_id, composition_mode, model, res_id, context)"/>
                         <field name="template_id" colspan="2" nolabel="1"
                             attrs="{'invisible':[('use_template','=',False)]}"
-                            on_change="on_change_template(use_template, template_id, False, False, context)"/>
+                            on_change="onchange_template_id(use_template, template_id, composition_mode, model, res_id, context)"/>
                     </xpath>
-                    <xpath expr="//a[@class='oe_mail_compose_message_checklist']" position="before">
+                    <xpath expr="//button[@class='oe_mail_compose_message_attachment']" position="before">
                         <button icon="/email_template/static/src/img/email_template.png"
-                            type="object" name="template_toggle" string=""
-                            help="Use a message template"/>
+                            type="object" name="toggle_template" string=""
+                            help="Use a message template"
+                            attrs="{'invisible':[('content_subtype','!=','html')]}"/>
                         <button icon="/email_template/static/src/img/email_template_save.png"
                             type="object" name="save_as_template" string=""
-                            help="Save as a new template"/>
+                            help="Save as a new template"
+                            attrs="{'invisible':[('content_subtype','!=','html')]}"/>
                     </xpath>
                 </data>
             </field>
index 2666ece..95ffbf4 100644 (file)
@@ -7,15 +7,12 @@
         <field name="email_from" >${object.user_id.email or object.company_id.email or 'noreply@' + object.company_id.name + '.com'}</field>
         <field name="email_to" >${object.email}</field>
         <field name="subject">Your registration at ${object.event_id.name}</field>
-        <field name="body_text">
-        hello ${object.name},
-
-        The event ${object.event_id.name} that you registered for is confirmed and will be held from ${object.event_id.date_begin} to ${object.event_id.date_end}. For any further information please contact our event department.
-
-        we thank you for your participation
-
-        best regards
-        </field>
+        <field name="body_html"><![CDATA[
+        <p>Hello ${object.name},</p>
+        <p>The event ${object.event_id.name} that you registered for is confirmed and will be held from ${object.event_id.date_begin} to ${object.event_id.date_end}.
+        For any further information please contact our event department.</p>
+        <p>Thank you for your participation!</p>
+        <p>Best regards</p>]]></field>
         </record>
 
     </data>
         <field name="email_from" >${object.user_id.email or object.company_id.email or 'noreply@' + object.company_id.name + '.com'}</field>
         <field name="email_to" >${object.email}</field>
         <field name="subject">Your registration at ${object.event_id.name}</field>
-        <field name="body_text">
-        hello ${object.name},
-
-        We confirm you that your registration to the event ${object.event_id.name} has been recorded. You will automatically receive an email providing you more practical information (such as the schedule, the plan...) as soon as the event will be confirmed to be held.
-
-        we thank you for your participation
-
-        best regards
-        </field>
+        <field name="body_html"><![CDATA[
+        <p>Hello ${object.name},</p>
+        <p>We confirm that your registration to the event ${object.event_id.name} has been recorded.
+        You will automatically receive an email providing you more practical information (such as the schedule, the agenda...) as soon as the event is confirmed.</p>
+        <p>Thank you for your participation!</p>
+        <p>Best regards</p>]]></field>
         </record>
 
         <!-- Default Values -->
index e4243a7..1612265 100644 (file)
 #
 ##############################################################################
 
-import time
 from osv import fields, osv
 from tools.translate import _
-import decimal_precision as dp
 from openerp import SUPERUSER_ID
 
 class event_type(osv.osv):
@@ -49,11 +47,11 @@ class event_event(osv.osv):
     _name = 'event.event'
     _description = __doc__
     _order = 'date_begin'
-    _inherit = ['ir.needaction_mixin','mail.thread']
+    _inherit = ['mail.thread','ir.needaction_mixin']
 
     def name_get(self, cr, uid, ids, context=None):
         if not ids:
-              return []
+            return []
         res = []
         for record in self.browse(cr, uid, ids, context=context):
             date = record.date_begin.split(" ")[0]
@@ -99,7 +97,6 @@ class event_event(osv.osv):
         return self.write(cr, uid, ids, {'state': 'done'}, context=context)
 
     def check_registration_limits(self, cr, uid, ids, context=None):
-        register_pool = self.pool.get('event.registration')
         for self.event in self.browse(cr, uid, ids, context=context):
             total_confirmed = self.event.register_current
             if total_confirmed < self.event.register_min or total_confirmed > self.event.register_max and self.event.register_max!=0:
@@ -109,7 +106,7 @@ class event_event(osv.osv):
         for event in self.browse(cr, uid, ids, context=context):
             available_seats = event.register_avail
             if available_seats and no_of_registration > available_seats:
-                 raise osv.except_osv(_('Warning!'),_("Only %d Seats are Available!") % (available_seats))
+                raise osv.except_osv(_('Warning!'),_("Only %d Seats are Available!") % (available_seats))
             elif available_seats == 0:
                 raise osv.except_osv(_('Warning!'),_("No Tickets Available!"))
 
@@ -139,7 +136,6 @@ class event_event(osv.osv):
         @param context: A standard dictionary for contextual values
         @return: Dictionary of function fields value.
         """
-        register_pool = self.pool.get('event.registration')
         res = {}
         for event in self.browse(cr, uid, ids, context=context):
             res[event.id] = {}
@@ -287,27 +283,27 @@ class event_event(osv.osv):
 
     def create_send_note(self, cr, uid, ids, context=None):
         message = _("Event has been <b>created</b>.")
-        self.message_append_note(cr, uid, ids, body=message, context=context)
+        self.message_post(cr, uid, ids, body=message, context=context)
         return True
 
     def button_cancel_send_note(self, cr, uid, ids, context=None):
         message = _("Event has been <b>cancelled</b>.")
-        self.message_append_note(cr, uid, ids, body=message, context=context)
+        self.message_post(cr, uid, ids, body=message, context=context)
         return True
 
     def button_draft_send_note(self, cr, uid, ids, context=None):
         message = _("Event has been set to <b>draft</b>.")
-        self.message_append_note(cr, uid, ids, body=message, context=context)
+        self.message_post(cr, uid, ids, body=message, context=context)
         return True
 
     def button_done_send_note(self, cr, uid, ids, context=None):
         message = _("Event has been <b>done</b>.")
-        self.message_append_note(cr, uid, ids, body=message, context=context)
+        self.message_post(cr, uid, ids, body=message, context=context)
         return True
 
     def button_confirm_send_note(self, cr, uid, ids, context=None):
         message = _("Event has been <b>confirmed</b>.")
-        self.message_append_note(cr, uid, ids, body=message, context=context)
+        self.message_post(cr, uid, ids, body=message, context=context)
         return True
 
 event_event()
@@ -353,7 +349,7 @@ class event_registration(osv.osv):
         return self.write(cr, uid, ids, {'state': 'draft'}, context=context)
 
     def confirm_registration(self, cr, uid, ids, context=None):
-        self.message_append(cr, uid, ids,_('State set to open'),body_text= _('Open'))
+        self.message_post(cr, uid, ids, body=_('State set to open'), context=context)
         return self.write(cr, uid, ids, {'state': 'open'}, context=context)
 
     def create(self, cr, uid, vals, context=None):
@@ -383,13 +379,13 @@ class event_registration(osv.osv):
             if today >= registration.event_id.date_begin:
                 values = {'state': 'done', 'date_closed': today}
                 self.write(cr, uid, ids, values)
-                self.message_append(cr, uid, ids, _('State set to Done'), body_text=_('Done'))
+                self.message_post(cr, uid, ids, body=_('State set to Done'), context=context)
             else:
                 raise osv.except_osv(_('Error!'),_("You must wait for the starting day of the event to do this action.") )
         return True
 
     def button_reg_cancel(self, cr, uid, ids, context=None, *args):
-        self.message_append(cr, uid, ids,_('State set to Cancel'),body_text= _('Cancel'))
+        self.message_post(cr, uid, ids, body=_('State set to Cancel'), context=context)
         return self.write(cr, uid, ids, {'state': 'cancel'})
 
     def mail_user(self, cr, uid, ids, context=None):
@@ -459,12 +455,12 @@ class event_registration(osv.osv):
 
     def create_send_note(self, cr, uid, ids, context=None):
         message = _("Registration has been <b>created</b>.")
-        self.message_append_note(cr, uid, ids, body=message, context=context)
+        self.message_post(cr, uid, ids, body=message, context=context)
         return True
 
     def do_draft_send_note(self, cr, uid, ids, context=None):
         message = _("Registration has been set as <b>draft</b>.")
-        self.message_append_note(cr, uid, ids, body=message, context=context)
+        self.message_post(cr, uid, ids, body=message, context=context)
         return True
 
 event_registration()
index d8d7595..2c19fdc 100644 (file)
         </record>
 
         <!-- notify all employees of module installation -->
-        <function model="mail.group" name="message_append_note">
-            <!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
-            <value eval="[ref('mail.group_all_employees')]"/>
-            <value>Module Events Organisation has been installed</value>
-            <value>From the top menu Events, you can organize events, manage registrations, automate communication around your event and sell events through your quotations.</value>
-        </function>
+        <record model="mail.message" id="module_install_notification">
+            <field name="model">mail.group</field>
+            <field name="res_id" ref="mail.group_all_employees"/>
+            <field name="type">notification</field>
+            <field name="subject">Events Organisation application installed!</field>
+            <field name="body">From the top Events menu, you can organize events, manage registrations, automate communication around your event and sell events through your quotations.</field>
+        </record>
+
     </data>
 </openerp>
index a9a4b11..e2ff2e6 100644 (file)
             <field name="name">event.event.tree</field>
             <field name="model">event.event</field>
             <field name="arch" type="xml">
-                <tree string="Events" fonts="bold:needaction_pending==True" colors="red:(register_min and register_min&gt;register_current) or (register_max and register_max&lt;register_current);grey:state=='cancel'">
+                <tree string="Events" fonts="bold:message_unread==True" colors="red:(register_min and register_min&gt;register_current) or (register_max and register_max&lt;register_current);grey:state=='cancel'">
                     <field name="name" string="Name"/>
                     <field name="type"/>
                     <field name="date_begin"/>
                     <field name="main_speaker_id" groups="base.extended"/>
                     <field name="user_id"/>
                     <field name="state"/>
-                    <field name="needaction_pending" invisible="1"/>
+                    <field name="message_unread" invisible="1"/>
                 </tree>
             </field>
         </record>
             <field name="arch" type="xml">
                 <search string="Events">
                     <field name="name" string="Events"/>
-                    <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="needaction_pending" domain="[('needaction_pending','=',True)]"/>
+                    <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/>
                     <separator/>
                     <filter icon="terp-check" string="Unconfirmed" name="draft" domain="[('state','=','draft')]" help="Events in New state"/>
                     <filter icon="terp-camera_test" string="Confirmed" domain="[('state','=','confirm')]" help="Confirmed events"/>
             <field name="name">event.registration.tree</field>
             <field name="model">event.registration</field>
             <field name="arch" type="xml">
-                <tree string="Registration" fonts="bold:needaction_pending==True">
+                <tree string="Registration" fonts="bold:message_unread==True">
                     <field name="create_date"/>
                     <field name="partner_id"/>
                     <field name="name"/>
                     <field name="user_id"/>
                     <field name="origin"/>
                     <field name="state"/>
-                    <field name="needaction_pending" invisible="1"/>
+                    <field name="message_unread" invisible="1"/>
                 </tree>
             </field>
         </record>
             <field name="arch" type="xml">
                 <search string="Event Registration">
                     <field name="name" string="Participant" filter_domain="['|','|','|',('name','ilike',self),('partner_id','ilike',self),('email','ilike',self),('origin','ilike',self)]"/>
-                    <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="needaction_pending" domain="[('needaction_pending','=',True)]"/>
+                    <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/>
                     <separator/>
                     <filter icon="terp-check" string="New" name="draft" domain="[('state','=','draft')]" help="Registrations in unconfirmed state"/>
                     <filter icon="terp-camera_test" string="Confirmed" domain="[('state','=','open')]" help="Confirmed registrations"/>
index 681116d..1d7a30e 100644 (file)
@@ -88,5 +88,5 @@ class sale_order_line(osv.osv):
                 }
                 registration_id = registration_obj.create(cr, uid, dic, context=context)
                 message = _("The registration %s has been created from the Sale Order %s.") % (registration_id, order_line.order_id.name)
-                registration_obj.message_append_note(cr, uid, [registration_id], body=message, context=context)
+                registration_obj.message_post(cr, uid, [registration_id], body=message, context=context)
         return super(sale_order_line, self).button_confirm(cr, uid, ids, context=context)
index 544a4f8..70a05c6 100644 (file)
@@ -77,7 +77,7 @@ class fetchmail_server(osv.osv):
                                                                                              "emails to the existing conversations (documents)."),
         'priority': fields.integer('Server Priority', readonly=True, states={'draft':[('readonly', False)]}, help="Defines the order of processing, "
                                                                                                                   "lower values mean higher priority"),
-        'message_ids': fields.one2many('mail.message', 'fetchmail_server_id', 'Messages', readonly=True),
+        'message_ids': fields.one2many('mail.mail', 'fetchmail_server_id', 'Messages', readonly=True),
         'configuration' : fields.text('Configuration'),
         'script' : fields.char('Script', readonly=True, size=64),
     }
@@ -232,8 +232,8 @@ openerp_mailgate.py -u %(uid)d -p PASSWORD -o %(model)s -d %(dbname)s --host=HOS
             server.write({'date': time.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)})
         return True
 
-class mail_message(osv.osv):
-    _inherit = "mail.message"
+class mail_mail(osv.osv):
+    _inherit = "mail.mail"
     _columns = {
         'fetchmail_server_id': fields.many2one('fetchmail.server', "Inbound Mail Server",
                                                readonly=True,
@@ -247,7 +247,7 @@ class mail_message(osv.osv):
         fetchmail_server_id = context.get('fetchmail_server_id')
         if fetchmail_server_id:
             values['fetchmail_server_id'] = fetchmail_server_id
-        res = super(mail_message,self).create(cr, uid, values, context=context)
+        res = super(mail_mail,self).create(cr, uid, values, context=context)
         return res
 
     def write(self, cr, uid, ids, values, context=None):
@@ -256,7 +256,7 @@ class mail_message(osv.osv):
         fetchmail_server_id = context.get('fetchmail_server_id')
         if fetchmail_server_id:
             values['fetchmail_server_id'] = server_id
-        res = super(mail_message,self).write(cr, uid, ids, values, context=context)
+        res = super(mail_mail,self).write(cr, uid, ids, values, context=context)
         return res
 
 
index cf651b5..3cdc886 100644 (file)
         />
 
       <record model="ir.ui.view" id="email_message_tree_view">
-          <field name="name">mail.message.list.fetchmail</field>
-          <field name="model">mail.message</field>
-          <field name="inherit_id" ref="mail.view_email_message_tree"/>
+          <field name="name">mail.mail.form.fetchmail</field>
+          <field name="model">mail.mail</field>
+          <field name="inherit_id" ref="mail.view_mail_form"/>
           <field name="arch" type="xml">
-              <field name="user_id" position="after">
+              <field name="references" position="after">
                   <field name="fetchmail_server_id"/>
               </field>
           </field>
       </record>
 
-      <record model="ir.ui.view" id="email_message_search_view">
-          <field name="name">mail.message.inherit.search</field>
-          <field name="model">mail.message</field>
-          <field name="inherit_id" ref="mail.view_email_message_search"/>
-          <field name="arch" type="xml">
-            <xpath expr="/search/group/filter[@string='Thread']" position="before">
-                <filter string="Mail Server" icon="terp-accessories-archiver" domain="[]" context="{'group_by':'fetchmail_server_id'}"/>
-            </xpath>
-          </field>
-      </record>
-
       <act_window
             context="{'search_default_server_id': active_id, 'default_fetchmail_server_id': active_id}"
             id="act_server_history" name="Messages" domain="[('email_from', '!=', False), ('fetchmail_server_id', '=', active_id)]"
index 966c29c..3c307b9 100644 (file)
@@ -212,7 +212,7 @@ class hr_employee(osv.osv):
         try:
             (model, mail_group_id) = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mail', 'group_all_employees')
             employee = self.browse(cr, uid, employee_id, context=context)
-            self.pool.get('mail.group').message_append_note(cr, uid, [mail_group_id], body='Welcome to %s! Please help him make its first steps in OpenERP!' % (employee.name), context=context)
+            self.pool.get('mail.group').message_post(cr, uid, [mail_group_id], body='Welcome to %s! Please help them take the first steps with OpenERP!' % (employee.name), context=context)
         except:
             pass # group deleted: do not push a message
         return employee_id
index 18c5083..514cf1f 100644 (file)
@@ -2,14 +2,16 @@
 <openerp>
     <data noupdate="1">
         <!-- notify all employees of module installation -->
-        <function model="mail.group" name="message_append_note">
-            <!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
-            <value eval="[ref('mail.group_all_employees')]"/>
-            <value>Module Employee Directory has been installed</value>
-            <value>Manage your human resources in OpenERP: employees and hierarchy, HR departments and jobs.
+        <record model="mail.message" id="module_install_notification">
+            <field name="model">mail.group</field>
+            <field name="res_id" ref="mail.group_all_employees"/>
+            <field name="type">notification</field>
+            <field name="subject">Employee Directory application installed!</field>
+            <field name="body">Manage your human resources with OpenERP: employees and their hierarchy, HR departments and job positions.
 
-More HR features are available from the following modules: Recruitment Process (manage job positions and recruitment), Timesheets Validation (record timesheets and attendance), Leaves Management (keep track of employee leaves), Expenses Management (manage employee expenses), Employee Appraisals (organize employee surveys, where employees evaluate their subordinates or their manager.)</value>
-        </function>
+More HR features are available via extra applications: Recruitment Process (manage job positions and recruitment), Timesheet Validation (record timesheets and attendance),
+Leave Management (keep track of employee leaves), Expense Management (manage employee expenses), Employee Appraisals (organize employee surveys, where employees evaluate their subordinates or their manager).</field>
+        </record>
 
         <record id="employee" model="hr.employee">
             <field name="name">Administrator</field>
index 2662b63..4be0f94 100644 (file)
@@ -38,11 +38,10 @@ created and it can be defined which level of employee hierarchy fills what and
 final review and evaluation is done by the manager. Every evaluation filled by
 the employees can be viewed in the form of pdf file.
          """,
-    'demo': ['hr_evaluation_demo.xml'],
-    'data': [
+    "demo": ["hr_evaluation_demo.xml"],
+    "data": [
         'security/ir.model.access.csv',
         'security/hr_evaluation_security.xml',
-#        'wizard/hr_evaluation_mail_view.xml',
         'hr_evaluation_view.xml',
         'report/hr_evaluation_report_view.xml',
         'board_hr_evaluation_view.xml',
index def21f2..3508dc3 100644 (file)
@@ -228,9 +228,13 @@ class hr_evaluation(osv.osv):
                         body = phase.mail_body % {'employee_name': child.name, 'user_signature': child.user_id.signature,
                             'eval_name': phase.survey_id.title, 'date': time.strftime('%Y-%m-%d'), 'time': time }
                         sub = phase.email_subject
-                        dest = [child.work_email]
-                        if dest:
-                           mail_message.schedule_with_attach(cr, uid, evaluation.employee_id.work_email, dest, sub, body, context=context)
+                        if child.work_email:
+                            vals = {'state': 'outgoing',
+                                    'subject': sub,
+                                    'body_html': '<pre>%s</pre>' % body,
+                                    'email_to': child.work_email,
+                                    'email_from': evaluation.employee_id.work_email}
+                            self.pool.get('mail.mail').create(cr, uid, vals, context=context)
 
         self.write(cr, uid, ids, {'state':'wait'}, context=context)
         return True
index cf632a7..2cf656a 100644 (file)
             <field name="sequence">100</field>
         </record>
         <!-- notify all employees of module installation -->
-        <function model="mail.group" name="message_append_note">
-            <!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
-            <value eval="[ref('mail.group_all_employees')]"/>
-            <value>Module Employee Appraisals has been installed</value>
-            <value>Create evaluations for your subordinates or manager. You can define a plan with several surveys, where you organize evaluation surveys related to the hierarchy levels. Evaluations filled by employees can exported as pdf files.</value>
-        </function>
+        <record model="mail.message" id="module_install_notification">
+            <field name="model">mail.group</field>
+            <field name="res_id" ref="mail.group_all_employees"/>
+            <field name="type">notification</field>
+            <field name="subject">Employee Appraisals application installed!</field>
+            <field name="body">Manage employee reviews: you can define an appraisal campaign with several steps, with specific evaluation surveys according to hierarchy levels.
+Evaluations filled by employees may be exported as pdf files.</field>
+        </record>
 
         <record id="survey_2" model="survey">
             <field name="title">Self Appraisal</field>
index 12c5dc0..4f49b7b 100644 (file)
             <field name="arch" type="xml">
                 <form string="Interview Appraisal" version="7.0">
                     <header>
-                        <button string="Cancel" name="survey_req_cancel" states="draft,waiting_answer" type="object" icon="gtk-cancel"/>
-                        <button name="action_print_survey" string="Print Survey" type="object" states="draft" icon="gtk-print" context="{'survey_id': survey_id, 'response_id': [response], 'response_no':0}" attrs="{'readonly':[('survey_id','=',False)]}" class="oe_highlight"/>
-                        <button string="Send Request" name="survey_req_waiting_answer" states="draft" type="object" icon="gtk-yes" class="oe_highlight"/>
-                        <button name="%(survey.action_view_survey_question_message)d" string="Answer Survey" type="action" states="waiting_answer" context="{'survey_id': survey_id, 'response_id': [response], 'response_no':0, 'active' : response,'request' : True, 'object' : 'hr.evaluation.interview', 'cur_id' : active_id}" attrs="{'readonly':[('survey_id','=',False)]}"/>
-                        <button string="Done" name="survey_req_done" states="waiting_answer" type="object" icon="gtk-jump-to"/>
+                        <button string="Cancel" name="survey_req_cancel" type="object"
+                            states="draft,waiting_answer"/>
+                        <button string="Print Survey" name="action_print_survey"  type="object"
+                            states="draft" context="{'survey_id': survey_id, 'response_id': [response], 'response_no':0}"
+                            attrs="{'readonly':[('survey_id','=',False)]}" class="oe_highlight"/>
+                        <button string="Send Request" name="survey_req_waiting_answer" type="object"
+                            states="draft" class="oe_highlight"/>
+                        <button string="Answer Survey" name="%(survey.action_view_survey_question_message)d" type="action"
+                            states="waiting_answer"
+                            context="{'survey_id': survey_id, 'response_id': [response], 'response_no':0, 'active' : response,'request' : True, 'object' : 'hr.evaluation.interview', 'cur_id' : active_id}"
+                            attrs="{'readonly':[('survey_id','=',False)]}"/>
+                        <button string="Done" name="survey_req_done" type="object"
+                            states="waiting_answer"/>
                         <field name="state" widget="statusbar" statusbar_visible="waiting_answer,done"/>
                     </header>
                     <sheet>
                         <div class="oe_right oe_button_box" name="button_box">
-                            <button string="Send Reminder Email" name="%(mail.action_email_compose_message_wizard)d" icon="terp-mail-message-new" type="action" states="waiting_answer"/>
+                            <button string="Send Reminder Email" name="%(mail.action_email_compose_message_wizard)d" type="action"
+                                states="waiting_answer"
+                                context="{'default_body_text': 'Hello,\n\nKindly post your response for the survey interview.\n\nThanks',
+                                            'default_subject': 'Reminder to fill up Survey' }"/>
                         </div>
                         <group>
                             <group col="3" colspan="1">
 
       <!-- Email Compose message Action-->
       <act_window
-      id="evaluation_reminders" name="Appraisal Reminders"
-      res_model="mail.compose.message"
-      src_model="hr.evaluation.interview"
-      view_type="form" view_mode="form"
-      target="new" multi="True"
-      key2="client_action_multi"
-      context="{'mail.compose.message.mode':'mass_mail'}"/>
+        id="evaluation_reminders" name="Appraisal Reminders"
+        res_model="mail.compose.message"
+        src_model="hr.evaluation.interview"
+        view_type="form" view_mode="form"
+        target="new" multi="True"
+        key2="client_action_multi"
+        context="{'default_composition_mode': 'mass_mail',
+                    'default_body_text': 'Hello,\n\nKindly post your response for the survey interview.\n\nThanks',
+                    'default_subject': 'Reminder to fill up Survey'}"/>
 
       <!-- Appraisal Interviews Button on Employee Form -->
       <act_window
-      context="{'search_default_user_to_review_id': [active_id], 'default_user_to_review_id': active_id}"
-      id="act_hr_employee_2_hr__evaluation_interview"
-      name="Appraisal Interviews"
-      res_model="hr.evaluation.interview"
-      src_model="hr.employee"/>
+        context="{'search_default_user_to_review_id': [active_id], 'default_user_to_review_id': active_id}"
+        id="act_hr_employee_2_hr__evaluation_interview"
+        name="Appraisal Interviews"
+        res_model="hr.evaluation.interview"
+        src_model="hr.employee"/>
 
     </data>
 </openerp>
diff --git a/addons/hr_evaluation/wizard/__init__.py b/addons/hr_evaluation/wizard/__init__.py
deleted file mode 100644 (file)
index 64406ac..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- encoding: utf-8 -*-
-##############################################################################
-#
-#    OpenERP, Open Source Management Solution
-#    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
-#
-#    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 hr_evaluation_mail
-import mail_compose_message
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/hr_evaluation/wizard/hr_evaluation_mail.py b/addons/hr_evaluation/wizard/hr_evaluation_mail.py
deleted file mode 100644 (file)
index 35c54a4..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-##############################################################################
-#
-#    OpenERP, Open Source Management Solution
-#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
-#
-#    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/>.
-#
-##############################################################################
-
-from osv import fields, osv
-import tools
-
-class hr_evaluation_reminder(osv.osv_memory):
-    _name = "hr.evaluation.reminder"
-    _description = "Sends Reminders to employees to fill the evaluations"
-    _columns = {
-        'evaluation_id': fields.many2one('hr.evaluation.interview', 'Interview', required=True)
-    }
-
-    def send_mail(self, cr, uid, ids, context=None):
-        mail_message = self.pool.get('mail.message')
-        hr_evaluation_interview_obj = self.pool.get('hr.evaluation.interview')
-        evaluation_data = self.read(cr, uid, ids, context=context)[0]
-        current_interview = hr_evaluation_interview_obj.browse(cr, uid, evaluation_data.get('evaluation_id'))
-        if current_interview.state == "waiting_answer" and current_interview.user_to_review_id.work_email :
-            msg = " Hello %s, \n\n Kindly post your response for '%s' survey interview. \n\n Thanks,"  %(current_interview.user_to_review_id.name, current_interview.survey_id.title)
-            mail_message.schedule_with_attach(cr, uid, tools.config['email_from'], [current_interview.user_to_review_id.work_email],\
-                                          'Reminder to fill up Survey', msg, context=context)
-        return {'type': 'ir.actions.act_window_close'}
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/hr_evaluation/wizard/hr_evaluation_mail_view.xml b/addons/hr_evaluation/wizard/hr_evaluation_mail_view.xml
deleted file mode 100644 (file)
index 89ae99b..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<openerp>
-    <data>
-
-    <record id="view_hr_evaluation_send_mail" model="ir.ui.view">
-        <field name="name">hr.evaluation.send.mail</field>
-        <field name="model">hr.evaluation.reminder</field>
-        <field name="arch" type="xml">
-            <form string="Evaluation Reminders" version="7.0">
-                <group string="Send Evaluation Reminder">
-                    <field name="evaluation_id"/>
-                </group>
-                <footer>
-                    <button name="send_mail" string="Send Mail" type="object" class="oe_highlight"/>
-                    or
-                    <button string="Cancel" class="oe_link" special="cancel" />
-                </footer>
-             </form>
-        </field>
-    </record>
-
-    <record id="action_hr_evaluation_send_mail" model="ir.actions.act_window">
-        <field name="name">Evaluation Send Mail</field>
-        <field name="res_model">hr.evaluation.reminder</field>
-        <field name="type">ir.actions.act_window</field>
-        <field name="view_type">form</field>
-        <field name="view_mode">tree,form</field>
-        <field name="view_id" ref="view_hr_evaluation_send_mail"/>
-        <field name="context">{'record_id':active_id}</field>
-        <field name="target">new</field>
-    </record>
-
-     </data>
-</openerp>
diff --git a/addons/hr_evaluation/wizard/mail_compose_message.py b/addons/hr_evaluation/wizard/mail_compose_message.py
deleted file mode 100644 (file)
index 7e4ba32..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-#    OpenERP, Open Source Management Solution
-#    Copyright (C) 2010-Today OpenERP SA (<http://www.openerp.com>)
-#
-#    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 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 General Public License for more details.
-#
-#    You should have received a copy of the GNU General Public License
-#    along with this program.  If not, see <http://www.gnu.org/licenses/>
-#
-##############################################################################
-
-from osv import osv
-from osv import fields
-import tools
-from tools.translate import _
-
-class mail_compose_message(osv.osv_memory):
-    _inherit = 'mail.compose.message'
-
-    def get_value(self, cr, uid, model, resource_id, context=None):
-        '''
-        To get values of the resource_id for the model
-        @param model: Object
-        @param resource_id: id of a record for which values to be read
-
-        @return: Returns a dictionary
-        '''
-        if context is None:
-            context = {}
-        result = super(mail_compose_message, self).get_value(cr, uid,  model, resource_id, context=context)
-        if model == 'hr.evaluation.interview' and resource_id:
-            model_pool = self.pool.get(model)
-            record_data = model_pool.browse(cr, uid, resource_id, context)
-            if record_data.state == "waiting_answer":
-                msg = _("Hello %s, \n\n Kindly post your response for '%s' survey interview. \n\n Thanks,")  %(record_data.user_to_review_id.name, record_data.survey_id.title)
-                result.update({
-                        'email_to': record_data.user_to_review_id.work_email or False,
-                        'subject': _("Reminder to fill up Survey"),
-                        'body_text': msg,
-                    })
-        return result
-
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
index 9c148ad..63fca88 100644 (file)
 
 
 {
-    'name': 'Expenses Management',
+    'name': 'Expense Management',
     'version': '1.0',
     'category': 'Human Resources',
     'sequence': 30,
     'summary': 'Expenses Validation, Invoicing',
     'description': """
-This module aims to manage employee's expenses.
-===============================================
+This module aims to manage employee expenses.
+=============================================
 
 The whole workflow is implemented:
 ----------------------------------
index 6e41f3d..2a9f3ae 100644 (file)
@@ -2,20 +2,20 @@
 <openerp>
     <data noupdate="1">
         <!-- notify all employees of module installation -->
-        <function model="mail.group" name="message_append_note">
-            <!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
-            <value eval="[ref('mail.group_all_employees')]"/>
-            <value>Module Expenses Management has been installed</value>
-            <value>Manage your employee's expenses, with validations by employee manager and accountant, creation and payment of invoices.
+        <record model="mail.message" id="module_install_notification">
+            <field name="model">mail.group</field>
+            <field name="res_id" ref="mail.group_all_employees"/>
+            <field name="type">notification</field>
+            <field name="subject">Expense Management application installed!</field>
+            <field name="body">Manage your employees' expenses, after due validation by their manager and the accountant, then generate and pay the corresponding invoices.
 
-This module also uses the analytic accounting and is compatible with the invoice on timesheet module so that you will be able to automatically re-invoice your customer's expenses if your work by project.</value>
-        </function>
+This feature is also linked to analytic accounting and compatible with timesheet invoices, so you will be able to automatically re-invoice project-related expenses to your customers.</field>
+        </record>
 
         <!-- Resource: product.uom.categ -->
         <record id="cat_expense" model="product.category">
             <field name="parent_id" ref="product.product_category_all"/>
             <field name="name">Expenses</field>
         </record>
-
     </data>
 </openerp>
index d5a2728..070ae03 100644 (file)
@@ -21,7 +21,7 @@
 
 
 {
-    'name': 'Leaves Management',
+    'name': 'Leave Management',
     'version': '1.5',
     'author': 'OpenERP SA',
     'category': 'Human Resources',
@@ -29,8 +29,8 @@
     'summary': 'Holidays, Allocation and Leave Requests',
     'website': 'http://www.openerp.com',
     'description': """
-This module allows you to manage leaves and leave's requests.
-=============================================================
+This module allows you to manage leaves and leave requests.
+===========================================================
 
 Implements a dashboard for human resource management that includes:
 -------------------------------------------------------------------
index f687e91..7591702 100644 (file)
@@ -95,7 +95,7 @@ class hr_holidays(osv.osv):
     _name = "hr.holidays"
     _description = "Leave"
     _order = "type desc, date_from asc"
-    _inherit = ['ir.needaction_mixin', 'mail.thread']
+    _inherit = [ 'mail.thread','ir.needaction_mixin']
 
     def _employee_get(self, cr, uid, context=None):
         ids = self.pool.get('hr.employee').search(cr, uid, [('user_id', '=', uid)], context=context)
@@ -344,62 +344,53 @@ class hr_holidays(osv.osv):
                     if leaves_rest < record.number_of_days_temp:
                         raise osv.except_osv(_('Warning!'), _('There are not enough %s allocated for employee %s; please create an allocation request for this leave type.') % (record.holiday_status_id.name, record.employee_id.name))
         return True
-    
+
     # -----------------------------
     # OpenChatter and notifications
     # -----------------------------
-    
-    def get_needaction_user_ids(self, cr, uid, ids, context=None):
-        result = super(hr_holidays, self).get_needaction_user_ids(cr, uid, ids, context=context)
-        for obj in self.browse(cr, uid, ids, context=context):
-            if obj.state == 'confirm' and obj.holiday_type == 'employee' and obj.employee_id.parent_id:
-                result[obj.id] = [obj.employee_id.parent_id.user_id.id]
-            elif obj.state == 'validate1':
-                # get group_hr_manager: everyone will be warned of second validation
-                res = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'group_hr_manager') or False
-                obj_id = res and res[1] or False
-                if obj_id:
-                    hr_manager_group = self.pool.get('res.groups').read(cr, uid, [obj_id], ['users'], context=context)[0]
-                    result[obj.id] = hr_manager_group['users']
-        return result
 
-    def message_get_monitored_follower_fields(self, cr, uid, ids, context=None):
-        """ Add 'user_id' and 'manager' to the monitored fields """
-        res = super(hr_holidays, self).message_get_monitored_follower_fields(cr, uid, ids, context=context)
-        return res + ['user_id']
-        
+    def needaction_domain_get(self, cr, uid, ids, context=None):
+        # to be tested, otherwise convert into employee_id in ...
+        emp_obj = self.pool.get('hr.employee')
+        empids = emp_obj.search(cr, uid, [('parent_id.user_id','=',uid)], context=context)
+        dom = [
+            '&', ('state','=','confirm'),('employee_id', 'in', empids)
+        ]
+        # if this user is a hr.manager, he should do second validations
+        if self.pool.get('res.users').has_group(cr, uid, 'base.group_hr_manager'):
+            dom = ['|'] + dom + [ ('state','=','validate1') ]
+        return dom
+
     def create_notificate(self, cr, uid, ids, context=None):
         for obj in self.browse(cr, uid, ids, context=context):
-            self.message_append_note(cr, uid, ids, _('System notification'),
-                    _("The request has been <b>created</b> and is waiting confirmation."), type='notification', context=context)
+            self.message_post(cr, uid, ids, 
+                _("The request has been <b>created</b> and is waiting confirmation."), context=context)
         return True
     
     def holidays_confirm_notificate(self, cr, uid, ids, context=None):
         for obj in self.browse(cr, uid, ids):
-            self.message_append_note(cr, uid, [obj.id], _('System notification'), 
-                    _("The request has been <b>submitted</b> and is waiting for validation by the manager."), type='notification')
+            self.message_post(cr, uid, [obj.id],
+                _("The request has been <b>submitted</b> and is waiting for validation by the manager."), context=context)
     
     def holidays_first_validate_notificate(self, cr, uid, ids, context=None):
         for obj in self.browse(cr, uid, ids, context=context):
-            self.message_append_note(cr, uid, [obj.id], _('System notification'),
-                    _("The request has been <b>approved</b>. A second validation is necessary and is now pending."), type='notification', context=context)
+            self.message_post(cr, uid, [obj.id],
+                _("The request has been <b>approved</b>. A second validation is necessary and is now pending."), context=context)
             
     def holidays_validate_notificate(self, cr, uid, ids, context=None):
         for obj in self.browse(cr, uid, ids):
             if obj.double_validation:
-                self.message_append_note(cr, uid, [obj.id], _('System notification'),
-                    _("The request has been <b>double validated</b>. The validation process is now over."), type='notification', context=context)
+                self.message_post(cr, uid, [obj.id], 
+                    _("The request has been <b>double validated</b>. The validation process is now over."), context=context)
             else:
-                self.message_append_note(cr, uid, [obj.id], _('System notification'),
-                    _("The request has been <b>approved</b>. The validation process is now over."), type='notification', context=context)
-    
+                self.message_post(cr, uid, [obj.id],
+                    _("The request has been <b>approved</b>. The validation process is now over."), context=context)
     
     def holidays_refuse_notificate(self, cr, uid, ids, context=None):
         for obj in self.browse(cr, uid, ids):
-            self.message_append_note(cr, uid, [obj.id], _('System notification'),
-                    _("The request has been <b>refused</b>. The validation process is now over."), type='notification', context=context)
-    
-hr_holidays()
+            self.message_post(cr, uid, [obj.id],
+                _("The request has been <b>refused</b>. The validation process is now over."),  context=context)
+
 
 class resource_calendar_leaves(osv.osv):
     _inherit = "resource.calendar.leaves"
index 9db0f2e..1e53409 100644 (file)
             <field name="sequence">100</field>
         </record>
         <!-- notify all employees of module installation -->
-        <function model="mail.group" name="message_append_note">
-            <!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
-            <value eval="[ref('mail.group_all_employees')]"/>
-            <value>Module Leaves Management has been installed</value>
-            <value>Manage employee leaves from the top menu "Human Resources". Employees can create leave requests that are validated by their manager and/or HR people.  Once validated, they are visible in the employee's calendar.  HR people can define leave types and allocate off-days for employees.</value>
-        </function>
+        <record model="mail.message" id="module_install_notification">
+            <field name="model">mail.group</field>
+            <field name="res_id" ref="mail.group_all_employees"/>
+            <field name="type">notification</field>
+            <field name="subject">Leave Management application installed!</field>
+            <field name="body">Manage employee leaves from the top menu "Human Resources". Employees can create leave requests that are validated by their manager and/or HR officers.
+Once validated, they are visible in the employee's calendar. HR officers can define leave types and allocate leaves to employees and employee categories.</field>
+        </record>
+
 
         <!-- Casual leave -->
         <record model="hr.holidays.status" id="holiday_status_cl">
index 650445b..699c08b 100644 (file)
@@ -44,7 +44,6 @@
             <field name="arch" type="xml">
                 <search string="Search Leave">
                     <field name="date_from"/>
-                    <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="needaction_pending" domain="[('needaction_pending','=',True)]"/>
                     <separator/>
                     <filter icon="terp-check" domain="[('state','=','draft')]" string="To Confirm"/>
                     <filter icon="terp-camera_test" domain="[('state','=','confirm')]" string="To Approve" name="approve"/>
             <field name="name">hr.holidays.allocation.tree</field>
             <field name="model">hr.holidays</field>
             <field name="arch" type="xml">
-                <tree fonts="bold:needaction_pending==True" colors="red:state == 'refuse';blue:state == 'draft';black:state in ('confirm','validate','validate1')" string="Allocation Requests">
-                    <field name="needaction_pending" invisible="1"/>
+                <tree colors="red:state == 'refuse';blue:state == 'draft';black:state in ('confirm','validate','validate1')" string="Allocation Requests">
                     <field name="employee_id"/>
                     <field name="holiday_type"/>
                     <field name="category_id"/>
             <field name="model">hr.holidays</field>
             <field name="priority">20</field>
             <field name="arch" type="xml">
-                <tree fonts="bold:needaction_pending==True" colors="red:state == 'refuse';blue:state == ' draft';black:state in ('confirm','validate','validate1')" string="Leaves Summary">
-                    <field name="needaction_pending" invisible="1"/>
+                <tree colors="red:state == 'refuse';blue:state == ' draft';black:state in ('confirm','validate','validate1')" string="Leaves Summary">
                     <field name="employee_id"/>
                     <field name="category_id" invisible="1"/>
                     <field name="department_id" invisible="1"/>
             <field name="name">hr.holidays.tree</field>
             <field name="model">hr.holidays</field>
             <field name="arch" type="xml">
-                <tree fonts="bold:needaction_pending==True" colors="red:state == 'refuse';blue:state == ' draft';black:state in ('confirm','validate','validate1')" string="Leave Requests">
-                    <field name="needaction_pending" invisible="1"/>
+                <tree colors="red:state == 'refuse';blue:state == ' draft';black:state in ('confirm','validate','validate1')" string="Leave Requests">
                     <field name="employee_id"/>
                     <field name="holiday_type" string="Mode" groups="base.group_hr_user"/>
                     <field name="name"/>
index 6b4839b..82f2c9c 100644 (file)
@@ -90,8 +90,7 @@ class hr_applicant(base_stage, osv.Model):
     _name = "hr.applicant"
     _description = "Applicant"
     _order = "id desc"
-    _inherit = ['ir.needaction_mixin', 'mail.thread']
-    _mail_compose_message = True
+    _inherit = ['mail.thread', 'ir.needaction_mixin']
 
     def _get_default_department_id(self, cr, uid, context=None):
         """ Gives default department by checking if present in the context """
@@ -161,13 +160,11 @@ class hr_applicant(base_stage, osv.Model):
                         date_create = datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S")
                         date_open = datetime.strptime(issue.date_open, "%Y-%m-%d %H:%M:%S")
                         ans = date_open - date_create
-                        date_until = issue.date_open
 
                 elif field in ['day_close']:
                     if issue.date_closed:
                         date_create = datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S")
                         date_close = datetime.strptime(issue.date_closed, "%Y-%m-%d %H:%M:%S")
-                        date_until = issue.date_closed
                         ans = date_close - date_create
                 if ans:
                     duration = float(ans.days)
@@ -328,14 +325,13 @@ class hr_applicant(base_stage, osv.Model):
         if custom_values is None: custom_values = {}
         custom_values.update({
             'name':  msg.get('subject') or _("No Subject"),
-            'description': msg.get('body_text'),
+            'description': msg.get('body'),
             'email_from': msg.get('from'),
             'email_cc': msg.get('cc'),
             'user_id': False,
         })
         if msg.get('priority'):
             custom_values['priority'] = msg.get('priority')
-        custom_values.update(self.message_partner_by_email(cr, uid, msg.get('from', False), context=context))
         return super(hr_applicant,self).message_new(cr, uid, msg, custom_values=custom_values, context=context)
 
     def message_update(self, cr, uid, ids, msg, update_vals=None, context=None):
@@ -360,7 +356,7 @@ class hr_applicant(base_stage, osv.Model):
             'revenue': 'planned_revenue',
             'probability': 'probability',
         }
-        for line in msg.get('body_text', '').split('\n'):
+        for line in msg.get('body', '').split('\n'):
             line = line.strip()
             res = tools.misc.command_re.match(line)
             if res and maps.get(res.group(1).lower(), False):
@@ -457,23 +453,18 @@ class hr_applicant(base_stage, osv.Model):
     # OpenChatter methods and notifications
     # -------------------------------------------------------
 
-    def message_get_monitored_follower_fields(self, cr, uid, ids, context=None):
-        """ Add 'user_id' to the monitored fields """
-        res = super(hr_applicant, self).message_get_monitored_follower_fields(cr, uid, ids, context=context)
-        return res + ['user_id']
-
     def stage_set_send_note(self, cr, uid, ids, stage_id, context=None):
         """ Override of the (void) default notification method. """
         if not stage_id: return True
         stage_name = self.pool.get('hr.recruitment.stage').name_get(cr, uid, [stage_id], context=context)[0][1]
-        return self.message_append_note(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context)
+        return self.message_post(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context)
 
     def case_get_note_msg_prefix(self, cr, uid, id, context=None):
                return 'Applicant'
 
     def case_open_send_note(self, cr, uid, ids, context=None):
         message = _("Applicant has been set <b>in progress</b>.")
-        return self.message_append_note(cr, uid, ids, body=message, context=context)
+        return self.message_post(cr, uid, ids, body=message, context=context)
 
     def case_close_send_note(self, cr, uid, ids, context=None):
         if context is None:
@@ -481,23 +472,23 @@ class hr_applicant(base_stage, osv.Model):
         for applicant in self.browse(cr, uid, ids, context=context):
             if applicant.emp_id:
                 message = _("Applicant has been <b>hired</b> and created as an employee.")
-                self.message_append_note(cr, uid, [applicant.id], body=message, context=context)
+                self.message_post(cr, uid, [applicant.id], body=message, context=context)
             else:
                 message = _("Applicant has been <b>hired</b>.")
-                self.message_append_note(cr, uid, [applicant.id], body=message, context=context)
+                self.message_post(cr, uid, [applicant.id], body=message, context=context)
         return True
 
     def case_cancel_send_note(self, cr, uid, ids, context=None):
         msg = 'Applicant <b>refused</b>.'
-        return self.message_append_note(cr, uid, ids, body=msg, context=context)
+        return self.message_post(cr, uid, ids, body=msg, context=context)
 
     def case_reset_send_note(self,  cr, uid, ids, context=None):
         message =_("Applicant has been set as <b>new</b>.")
-        return self.message_append_note(cr, uid, ids, body=message, context=context)
+        return self.message_post(cr, uid, ids, body=message, context=context)
 
     def create_send_note(self, cr, uid, ids, context=None):
         message = _("Applicant has been <b>created</b>.")
-        return self.message_append_note(cr, uid, ids, body=message, context=context)
+        return self.message_post(cr, uid, ids, body=message, context=context)
 
 
 class hr_job(osv.osv):
index d90f5ab..84289da 100644 (file)
@@ -2,14 +2,15 @@
 <openerp>
 <data noupdate="1">
         <!-- notify all employees of module installation -->
-        <function model="mail.group" name="message_append_note">
-            <!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
-            <value eval="[ref('mail.group_all_employees')]"/>
-            <value>Module Recruitment Process has been installed</value>
-            <value>Manage job positions and the recruitment process in your company.  This module is integrated with the module Survey to help you to define interviews for different jobs.
+        <record model="mail.message" id="module_install_notification">
+            <field name="model">mail.group</field>
+            <field name="res_id" ref="mail.group_all_employees"/>
+            <field name="type">notification</field>
+            <field name="subject">Recruitment Process application installed!</field>
+            <field name="body">Manage job positions and your company's recruitment process.  This application is integrated with the Survey application to help you define interviews for different jobs.
 
-You can automatically create application records from an email gateway, that you can configure in the Human Resources Settings.</value>
-        </function>
+You can automatically receive job application though an email gateway, see the Human Resources settings.</field>
+        </record>
 
     <!-- Meeting Types (for interview meetings) -->
     <record model="crm.meeting.type" id="categ_meet_interview">
index 9fa8907..cea3b12 100644 (file)
@@ -76,8 +76,8 @@
         <field name="name">Applicants</field>
         <field name="model">hr.applicant</field>
         <field name="arch" type="xml">
-            <tree string="Applicants" fonts="bold:needaction_pending==True" colors="grey:state in ('cancel','done');blue:state=='pending'">
-                <field name="needaction_pending" invisible="1"/>
+            <tree string="Applicants" fonts="bold:message_unread==True" colors="grey:state in ('cancel','done');blue:state=='pending'">
+                <field name="message_unread" invisible="1"/>
                 <field name="create_date" groups="base.group_no_one"/>
                 <field name="name" string="Subject"/>
                 <field name="partner_name"/>
         <field name="arch" type="xml">
             <search string="Search Jobs">
                 <field name="partner_name" filter_domain="['|','|',('name','ilike',self),('partner_name','ilike',self),('email_from','ilike',self)]" string="Subject / Applicant"/>
-                <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="needaction_pending" domain="[('needaction_pending','=',True)]"/>
+                <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/>
                 <separator/>
                 <filter icon="terp-document-new" string="New" domain="[('state','=','draft')]" help="All Initial Jobs"/>
                 <filter icon="terp-camera_test" string="In Progress" domain="[('state','=','open')]" help="Open Jobs"/>
index 4cba4a1..a1562d5 100644 (file)
@@ -95,25 +95,25 @@ class account_analytic_account(osv.osv):
     def set_close(self, cr, uid, ids, context=None):
         self.write(cr, uid, ids, {'state':'close'}, context=context)
         message = _("Contract has been <b>closed</b>.")
-        self.message_append_note(cr, uid, ids, body=message, context=context)
+        self.message_post(cr, uid, ids, body=message, context=context)
         return True
 
     def set_cancel(self, cr, uid, ids, context=None):
         self.write(cr, uid, ids, {'state':'cancelled'}, context=context)
         message = _("Contract has been <b>cancelled</b>.")
-        self.message_append_note(cr, uid, ids, body=message, context=context)
+        self.message_post(cr, uid, ids, body=message, context=context)
         return True
 
     def set_open(self, cr, uid, ids, context=None):
         self.write(cr, uid, ids, {'state':'open'}, context=context)
         message = _("Contract has been <b>opened</b>.")
-        self.message_append_note(cr, uid, ids, body=message, context=context)
+        self.message_post(cr, uid, ids, body=message, context=context)
         return True
 
     def set_pending(self, cr, uid, ids, context=None):
         self.write(cr, uid, ids, {'state':'pending'}, context=context)
         message = _("Contract has been set as <b>pending</b>.")
-        self.message_append_note(cr, uid, ids, body=message, context=context)
+        self.message_post(cr, uid, ids, body=message, context=context)
         return True
 
 account_analytic_account()
index 34fbb60..b1f03e7 100644 (file)
@@ -27,8 +27,8 @@
     'sequence': 16,
     'summary': 'Timesheets, Attendances, Activities',
     'description': """
-This module helps you to easily encode and validate timesheet and attendances within the same view.
-===================================================================================================
+This module helps you to easily record and validate timesheets and attendances within the same view.
+====================================================================================================
 
     * It will maintain attendances and track (sign in/sign out) events.
     * Track the timesheet lines.
index 5458f39..87edf94 100644 (file)
@@ -1,24 +1,23 @@
 <?xml version="1.0" ?>
 <openerp>
     <data noupdate="1">
-
-        <!-- Notify all employees of module installation -->
-        <function model="mail.group" name="message_append_note">
-            <!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
-            <value eval="[ref('mail.group_all_employees')]"/>
-            <value>Module Timesheets Validation has been installed</value>
-            <value>From the top menu "Human Resources", encode and validate timesheets and attendances.</value>
-        </function>
+        <!-- notify all employees of module installation -->
+        <record model="mail.message" id="hr_timesheet_module_install_notification">
+            <field name="model">mail.group</field>
+            <field name="res_id" ref="mail.group_all_employees"/>
+            <field name="type">notification</field>
+            <field name="subject">Timesheet Validation application installed!</field>
+            <field name="body">From the top menu "Human Resources", enter and validate timesheets and attendances.</field>
+        </record>
 
         <record id="ir_actions_server_timsheet_sheet" model="ir.actions.server">
-            <field eval="5" name="sequence"/>
-            <field eval="&quot;&quot;&quot;code&quot;&quot;&quot;" name="state"/>
-            <field eval="&quot;&quot;&quot;ir.actions.server&quot;&quot;&quot;" name="type"/>
+            <field name="sequence" eval="5"/>
+            <field name="state">code</field>
+            <field name="type">ir.actions.server</field>
             <field name="model_id" ref="model_hr_timesheet_current_open"/>
-            <field eval="[(6,0,[])]" name="child_ids"/>
-            <field eval="&quot;&quot;&quot;action = pool.get('hr.timesheet.current.open').open_timesheet(cr, uid, None, context)&quot;&quot;&quot;" name="code"/>
-            <field eval="&quot;&quot;&quot;True&quot;&quot;&quot;" name="condition"/>
-            <field eval="&quot;&quot;&quot;My Timesheet&quot;&quot;&quot;" name="name"/>
+            <field name="code">action = pool.get('hr.timesheet.current.open').open_timesheet(cr, uid, None, context)</field>
+            <field name="condition">True</field>
+            <field name="name">My Timesheet</field>
         </record>
 
         <menuitem name="My Current Timesheet" id="menu_act_hr_timesheet_sheet_form_my_current" parent="hr_attendance.menu_hr_time_tracking" action="ir_actions_server_timsheet_sheet" sequence="1"/>
index 3be4dd2..e2e5ec5 100644 (file)
@@ -67,21 +67,21 @@ class idea_idea(osv.osv):
 
     def idea_cancel(self, cr, uid, ids, context={}):
         self.write(cr, uid, ids, { 'state': 'cancel' })
-        self.message_append_note(cr, uid, ids, body=_('Idea canceled.'), context=context)
+        self.message_post(cr, uid, ids, body=_('Idea canceled.'), context=context)
         return True
 
     def idea_open(self, cr, uid, ids, context={}):
         self.write(cr, uid, ids, { 'state': 'open'})
-        self.message_append_note(cr, uid, ids, body=_('Idea accepted.'), context=context)
+        self.message_post(cr, uid, ids, body=_('Idea accepted.'), context=context)
         return True
 
     def idea_close(self, cr, uid, ids, context={}):
-        self.message_append_note(cr, uid, ids, body=_('Idea done.'), context=context)
+        self.message_post(cr, uid, ids, body=_('Idea done.'), context=context)
         self.write(cr, uid, ids, { 'state': 'close' })
         return True
 
     def idea_draft(self, cr, uid, ids, context={}):
-        self.message_append_note(cr, uid, ids, body=_('Idea reset to draft.'), context=context)
+        self.message_post(cr, uid, ids, body=_('Idea reset to draft.'), context=context)
         self.write(cr, uid, ids, { 'state': 'draft' })
         return True
 idea_idea()
index 74d2185..7e0a055 100644 (file)
@@ -425,7 +425,7 @@ class import_framework(Thread):
         email_id = email_obj.create(self.cr, self.uid, {
             'email_from' : 'import@module.openerp',
             'email_to' : self.email,
-            'body_text' : self.get_email_body(result, error),
+            'body' : self.get_email_body(result, error),
             'subject' : self.get_email_subject(result, error),
             'auto_delete' : True})
         email_obj.send(self.cr, self.uid, [email_id])
index 440a7b2..1646fb7 100644 (file)
@@ -226,7 +226,7 @@ class sugar_import(import_framework):
                         'model': 'model',
                         'partner_id/.id': 'partner_id/.id',                         
                         'user_id/id': ref(self.TABLE_USER, 'assigned_user_id'),
-                        'body_text': 'description',
+                        'body': 'description',
                         'body_html' : 'description_html',
                         
                 }
index 289b908..da517ee 100644 (file)
 ##############################################################################
 
 import mail_alias
-import mail_message
 import mail_followers
+import mail_message
+import mail_mail
 import mail_thread
 import mail_group
-import ir_needaction
 import res_partner
 import res_users
 import report
index daec8d0..99dd6bd 100644 (file)
@@ -64,6 +64,7 @@ The main features of the module are:
         'wizard/mail_compose_message_view.xml',
         'res_config_view.xml',
         'mail_message_view.xml',
+        'mail_mail_view.xml',
         'mail_followers_view.xml',
         'mail_thread_view.xml',
         'mail_group_view.xml',
index e3d7c2a..1350fd3 100644 (file)
@@ -3,49 +3,43 @@
     <data noupdate="1">
 
         <record id="message_blogpost0" model="mail.message">
-            <field name="subject">Internal company announce</field>
             <field name="model">mail.group</field>
-            <field name="res_id" ref="group_all_employees"/>
-            <field name="content_subtype">html</field>
-            <field name="body_html"><![CDATA[Your monthly meal vouchers arrived. You can get them at Christine's office.
-This month you also have 250 EUR of eco-checks for all employees that worked with us since 1 year minimum.]]></field>
+            <field name="res_id" ref="mail.group_all_employees"/>
+            <field name="body">Your monthly meal vouchers arrived. You can get them at Christine's office.
+This month you also get 250 EUR of eco-vouchers if you have been in the company for more than a year.</field>
             <field name="type">comment</field>
-            <field name="user_id" ref="base.user_root"/>
         </record>
 
         <record id="message_blogpost0_comment0" model="mail.message">
             <field name="model">mail.group</field>
             <field name="res_id" ref="group_all_employees"/>
-            <field name="content_subtype">html</field>
-            <field name="body_html"><![CDATA[Great.]]></field>
+            <field name="body"><![CDATA[Great.]]></field>
             <field name="parent_id" ref="message_blogpost0"/>
             <field name="type">comment</field>
-            <field name="user_id" ref="base.user_root"/>
         </record>
 
         <record id="message_blogpost0_comment1" model="mail.message">
             <field name="model">mail.group</field>
             <field name="res_id" ref="group_all_employees"/>
-            <field name="content_subtype">html</field>
-            <field name="body_html"><![CDATA[Yes, that's a good news.]]></field>
+            <field name="body">Thanks, but where is Christine's office, if I may ask? (I'm new here)</field>
             <field name="parent_id" ref="message_blogpost0"/>
             <field name="type">comment</field>
-            <field name="user_id" ref="base.user_demo"/>
         </record>
 
         <record id="message_blogpost0_comment2" model="mail.message">
             <field name="model">mail.group</field>
             <field name="res_id" ref="group_all_employees"/>
-            <field name="content_subtype">html</field>
-            <field name="body_html"><![CDATA[Sure: Curabitur tempor bibendum diam, et euismod ante rutrum vel.
-
-In quis purus neque. Integer sodales dolor eu elit fringilla blandit. Maecenas lacus nisi, facilisis sit amet viverra eu, tristique vel augue.
-Donec viverra congue dui eu blandit. In lacinia molestie nulla, ut sagittis risus feugiat eu.
-
-Check the file in attachment for more information !  (third comment)]]></field>
+            <field name="body">Great news, I need to buy a new fridge, I think I can pay it with the eco-vouchers!</field>
             <field name="parent_id" ref="message_blogpost0"/>
             <field name="type">comment</field>
-            <field name="user_id" ref="base.user_root"/>
+        </record>
+
+        <record id="message_blogpost0_comment1_2" model="mail.message">
+            <field name="model">mail.group</field>
+            <field name="res_id" ref="group_all_employees"/>
+            <field name="body">Building B3, second floor on the right :-)</field>
+            <field name="parent_id" ref="message_blogpost0_comment1"/>
+            <field name="type">comment</field>
         </record>
 
         <record model="ir.config_parameter" id="user_mail_alias">
index 2152706..5372092 100644 (file)
         </record>
 
         <!-- notify all employees of module installation -->
-        <function model="mail.group" name="message_append_note">
-            <!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
-            <value eval="[ref('mail.group_all_employees')]"/>
-            <value>Welcome to OpenERP!</value>
-            <value>Your homepage is a summary of messages you received and key information about documents you follow.
+        <record model="mail.message" id="module_install_notification">
+            <field name="model">mail.group</field>
+            <field name="res_id" ref="mail.group_all_employees"/>
+            <field name="type">notification</field>
+            <field name="subject">Welcome to OpenERP!</field>
+            <field name="body">Your homepage is a summary of messages you received and key information about documents you follow.
 
 The top menu bar contains all applications you installed. You can use this &lt;i&gt;Settings&lt;/i&gt; menu to install more applications, activate others features or give access to new users.
 
-To setup your preferences (name, email signature, avatar), click on the top right corner.</value>
-        </function>
-
+To setup your preferences (name, email signature, avatar), click on the top right corner.</field>
+        </record>
     </data>
 </openerp>
index 789acdd..e4c86ad 100644 (file)
@@ -57,7 +57,7 @@ Use the thread viewer widget inside your form view by using the mail_thread widg
 Send notifications
 +++++++++++++++++++
 
-When sending a notification is required in your workflow or business logic, use the ``message_append_note`` method. This method is a shortcut to the ``message_append`` method that takes all ``mail.message`` fields as arguments. This latter method calls ``message_create`` that
+When sending a notification is required in your workflow or business logic, use the ``message_post`` method. This method is a shortcut to the ``message_append`` method that takes all ``mail.message`` fields as arguments. This latter method calls ``message_create`` that
 
  - creates the message
  - parses the body to find users you want to push the message to (finding and parsing ``@login`` in the message body)
@@ -74,7 +74,7 @@ You should therefore not worry about subscriptions or anything else than sending
     return res
 
   def do_something_send_note(self, cr, uid, ids, context=None):
-    self.message_append_note(cr, uid, ids, _('My subject'),
+    self.message_post(cr, uid, ids, _('My subject'),
     _("has received a <b>notification</b> and is happy for it."), context=context)
 
 Notifications guidelines
@@ -147,7 +147,7 @@ The best way to direct the messages that will be displayed in the OpenChatter wi
         # add: search in the current task project messages
         '&', '&', ('res_id', '=', my_task.project_id.id), ('model', '=', 'project.project'),
         # ... containing the task name
-        '|', ('body_text', 'like', '%s' % (my_task.name)), ('body_html', 'like', '%s' % (my_task.name))
+        '|', ('body', 'like', '%s' % (my_task.name)), ('body_html', 'like', '%s' % (my_task.name))
         ] + domain, limit=limit, offset=offset, context=context)
     # if asked: add ancestor ids to have complete threads
     if (ascent): msg_ids = self._message_add_ancestor_ids(cr, uid, ids, msg_ids, root_ids, context=context)
index 527a31b..fc2772b 100644 (file)
@@ -1,15 +1,15 @@
 .. _mail_state:
 
-message_state
+message_unread
 =============
 
-``message_state`` is a boolean field that states whether the document
+``message_unread`` is a boolean field that states whether the document
 has unread messages. In previous versions, some documents were going
 back to ``pending`` state when receiving an email through the mail
 gateway. Now the state related to messages differs from the state or
 stage of the document itself.
 
-message_state and need action mechanism
+message_unread and need action mechanism
 +++++++++++++++++++++++++++++++++++++++
 
 The ``mail`` module introduces a default behavior for the need_action
@@ -23,6 +23,6 @@ mechanism [REF].
     """
     result = super(ir_needaction_mixin, self).get_needaction_user_ids(cr, uid, ids, context=context)
     for obj in self.browse(cr, uid, ids, context=context):
-      if obj.message_state == False and obj.user_id:
+      if obj.message_unread == False and obj.user_id:
         result[obj.id].append(obj.user_id.id)
     return result
diff --git a/addons/mail/ir_needaction.py b/addons/mail/ir_needaction.py
deleted file mode 100644 (file)
index 8193ac4..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-#    OpenERP, Open Source Management Solution
-#    Copyright (C) 2012-today OpenERP SA (<http://www.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/>
-#
-##############################################################################
-
-from osv import osv
-
-class ir_needaction_mixin(osv.Model):
-    """ Update of ir.needaction_mixin class
-        - override the get_needaction_user_ids method to define the default
-          mail gateway need_action: when the object is unread, the object
-          responsible has an action to perform.
-    """
-    _name = 'ir.needaction_mixin'
-    _inherit = ['ir.needaction_mixin']
-    
-    def get_needaction_user_ids(self, cr, uid, ids, context=None):
-        """ Returns the user_ids that have to perform an action. It the
-            document mail state is unread (False), return object.user_id.id
-            as need_action uid.
-            :return: dict { record_id: [user_ids], }
-        """
-        result = super(ir_needaction_mixin, self).get_needaction_user_ids(cr, uid, ids, context=context)
-        for obj in self.browse(cr, uid, ids, context=context):
-            if obj.message_state == False and obj.user_id:
-                result[obj.id].append(obj.user_id.id)
-        return result
-
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
index fcdc78d..2aba685 100644 (file)
 
 from osv import osv
 from osv import fields
+import tools
 
 class mail_followers(osv.Model):
     """ mail_followers holds the data related to the follow mechanism inside
-    OpenERP. Users can choose to follow documents (records) of any kind that
-    inherits from mail.thread. Following documents allow to receive
-    notifications for new messages.
-    A subscription is characterized by:
-        :param: res_model: model of the followed objects
-        :param: res_id: ID of resource (may be 0 for every objects)
-        :param: user_id: user_id of the follower
+        OpenERP. Partners can choose to follow documents (records) of any kind
+        that inherits from mail.thread. Following documents allow to receive
+        notifications for new messages.
+        A subscription is characterized by:
+            :param: res_model: model of the followed objects
+            :param: res_id: ID of resource (may be 0 for every objects)
     """
     _name = 'mail.followers'
-    _rec_name = 'id'
+    _rec_name = 'partner_id'
     _log_access = False
-    _order = 'res_model asc'
-    _description = 'Mail Document Followers'
+    _description = 'Document Followers'
     _columns = {
         'res_model': fields.char('Related Document Model', size=128,
                         required=True, select=1,
                         help='Model of the followed resource'),
         'res_id': fields.integer('Related Document ID', select=1,
                         help='Id of the followed resource'),
-        'user_id': fields.many2one('res.users', string='Related User',
+        'partner_id': fields.many2one('res.partner', string='Related Partner',
                         ondelete='cascade', required=True, select=1),
     }
 
+
 class mail_notification(osv.Model):
-    """ mail_notification is a relational table modeling messages pushed to users.
-    """
+    """ Class holding notifications pushed to partners. Followers and partners
+        added in 'contacts to notify' receive notifications. """
     _name = 'mail.notification'
-    _rec_name = 'id'
+    _rec_name = 'partner_id'
     _log_access = False
-    _order = 'message_id desc'
-    _description = 'Mail notification'
+    _description = 'Notifications'
+
     _columns = {
-        'user_id': fields.many2one('res.users', string='User',
-                        ondelete='cascade', required=True, select=1),
+        'partner_id': fields.many2one('res.partner', string='Contact',
+                        ondelete='cascade', required=True),
+        'read': fields.boolean('Read'),
         'message_id': fields.many2one('mail.message', string='Message',
-                        ondelete='cascade', required=True, select=1),
+                        ondelete='cascade', required=True),
+    }
+
+    _defaults = {
+        'read': False,
     }
+
+    def init(self, cr):
+        cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('mail_notification_partner_id_read_message_id',))
+        if not cr.fetchone():
+            cr.execute('CREATE INDEX mail_notification_partner_id_read_message_id ON mail_notification (partner_id, read, message_id)')
+
+    def create(self, cr, uid, vals, context=None):
+        """ Override of create to check that we can not create a notification
+            for a message the user can not read. """
+        if self.pool.get('mail.message').check_access_rights(cr, uid, 'read'):
+            return super(mail_notification, self).create(cr, uid, vals, context=context)
+        return False
+
+    def set_message_read(self, cr, uid, msg_id, context=None):
+        partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
+        notif_ids = self.search(cr, uid, [('partner_id', '=', partner_id), ('message_id', '=', msg_id)], context=context)
+        return self.write(cr, uid, notif_ids, {'read': True}, context=context)
+
+    def notify(self, cr, uid, partner_ids, msg_id, context=None):
+        """ Send by email the notification depending on the user preferences """
+        context = context or {}
+        # mail_noemail (do not send email) or no partner_ids: do not send, return
+        if context.get('mail_noemail') or not partner_ids:
+            return True
+
+        mail_mail = self.pool.get('mail.mail')
+        msg = self.pool.get('mail.message').browse(cr, uid, msg_id, context=context)
+
+        # add signature
+        body_html = msg.body
+        signature = msg.author_id and msg.author_id.user_ids[0].signature or ''
+        if signature:
+            body_html = tools.append_content_to_html(body_html, signature)
+
+        mail_values = {
+            'mail_message_id': msg.id,
+            'email_to': [],
+            'auto_delete': True,
+            'body_html': body_html,
+            'state': 'outgoing',
+        }
+
+        for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids, context=context):
+            # Do not send an email to the writer
+            if partner.user_ids and partner.user_ids[0].id == uid:
+                continue
+            # Do not send to partners without email address defined
+            if not partner.email:
+                continue
+            # Partner does not want to receive any emails
+            if partner.notification_email_send == 'none':
+                continue
+            # Partner wants to receive only emails and comments
+            if partner.notification_email_send == 'comment' and msg.type not in ('email', 'comment'):
+                continue
+            # Partner wants to receive only emails
+            if partner.notification_email_send == 'email' and msg.type != 'email':
+                continue
+            if partner.email not in mail_values['email_to']:
+                mail_values['email_to'].append(partner.email)
+        if mail_values['email_to']:
+            mail_values['email_to'] = ', '.join(mail_values['email_to'])
+            email_notif_id = mail_mail.create(cr, uid, mail_values, context=context)
+            mail_mail.send(cr, uid, [email_notif_id], context=context)
+        return True
index 62e9343..36f08b3 100644 (file)
@@ -6,13 +6,12 @@
         <record model="ir.ui.view" id="view_followers_tree">
             <field name="name">mail.followers.tree</field>
             <field name="model">mail.followers</field>
-            <field name="type">tree</field>
             <field name="priority">10</field>
             <field name="arch" type="xml">
                 <tree string="Followers">
                     <field name="res_model"/>
                     <field name="res_id"/>
-                    <field name="user_id"/>
+                    <field name="partner_id"/>
                 </tree>
             </field>
         </record>
@@ -24,7 +23,7 @@
             <field name="priority">10</field>
             <field name="arch" type="xml">
                 <tree string="Notifications">
-                    <field name="user_id"/>
+                    <field name="partner_id"/>
                     <field name="message_id"/>
                 </tree>
             </field>
@@ -38,7 +37,7 @@
         </record>
 
         <record id="action_view_notifications" model="ir.actions.act_window">
-            <field name="name">Pushed notif</field>
+            <field name="name">Notifications</field>
             <field name="res_model">mail.notification</field>
             <field name="view_type">form</field>
             <field name="view_mode">tree,form</field>
index 6a14fef..fdfeafd 100644 (file)
@@ -28,13 +28,8 @@ from osv import fields
 from tools.translate import _
 
 class mail_group(osv.Model):
-    """
-    A mail_group is a collection of users sharing messages in a discussion
-    group. Group users are users that follow the mail group, using the
-    subscription/follow mechanism of OpenSocial. A mail group has nothing
-    in common with res.users.group.
-    """
-    
+    """ A mail_group is a collection of users sharing messages in a discussion
+        group. The group mechanics are based on the followers. """
     _description = 'Discussion group'
     _name = 'mail.group'
     _inherit = ['mail.thread']
@@ -45,31 +40,16 @@ class mail_group(osv.Model):
         for obj in self.browse(cr, uid, ids, context=context):
             result[obj.id] = tools.image_get_resized_images(obj.image)
         return result
-    
+
     def _set_image(self, cr, uid, id, name, value, args, context=None):
         return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context)
-    
-    def _get_last_month_msg_nbr(self, cr, uid, ids, name, args, context=None):
-        result = {}
-        for id in ids:
-            lower_date = (DT.datetime.now() - DT.timedelta(days=30)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
-            result[id] = self.message_search(cr, uid, [id], limit=None, domain=[('date', '>=', lower_date)], count=True, context=context)
-        return result
-    
-    def _get_default_image(self, cr, uid, context=None):
-        image_path = openerp.modules.get_module_resource('mail', 'static/src/img', 'groupdefault.png')
-        return tools.image_resize_image_big(open(image_path, 'rb').read().encode('base64'))
-    
+
     _columns = {
         'description': fields.text('Description'),
         'menu_id': fields.many2one('ir.ui.menu', string='Related Menu', required=True, ondelete="cascade"),
-        'responsible_id': fields.many2one('res.users', string='Responsible',
-            ondelete='set null', required=True, select=1,
-            help="Responsible of the group that has all rights on the record."),
-        'public': fields.selection([('public', 'Public'), ('private', 'Private'), ('groups', 'Selected Group Only')],
-            string='Privacy', required=True,
-            help='This group is visible by non members. '\
-                 'Invisible groups can add members through the invite button.'),
+        'public': fields.selection([('public','Public'),('private','Private'),('groups','Selected Group Only')], 'Privacy', required=True,
+            help='This group is visible by non members. \
+            Invisible groups can add members through the invite button.'),
         'group_public_id': fields.many2one('res.groups', string='Authorized Group'),
         'group_ids': fields.many2many('res.groups', rel='mail_group_res_group_rel',
             id1='mail_group_id', id2='groups_id', string='Auto Subscription',
@@ -79,7 +59,7 @@ class mail_group(osv.Model):
         'image': fields.binary("Photo",
             help="This field holds the image used as photo for the "\
                  "user. The image is base64 encoded, and PIL-supported. "\
-                 "It is limited to a 12024x1024 px image."),
+                 "It is limited to a 1024x1024 px image."),
         'image_medium': fields.function(_get_image, fnct_inv=_set_image,
             string="Medium-sized photo", type="binary", multi="_get_image",
             store = {
@@ -96,17 +76,19 @@ class mail_group(osv.Model):
             help="Small-sized photo of the group. It is automatically "\
                  "resized as a 50x50px image, with aspect ratio preserved. "\
                  "Use this field anywhere a small image is required."),
-        'last_month_msg_nbr': fields.function(_get_last_month_msg_nbr, type='integer',
-            string='Messages count for last month'),
-        'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="cascade", 
-                                    help="The email address associated with this group. New emails received will automatically "
-                                         "create new topics."),
+        'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="cascade", required=True,
+            help="The email address associated with this group. New emails received will automatically "
+                 "create new topics."),
     }
 
     def _get_default_employee_group(self, cr, uid, context=None):
         ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'group_user')
         return ref and ref[1] or False
 
+    def _get_default_image(self, cr, uid, context=None):
+        image_path = openerp.modules.get_module_resource('mail', 'static/src/img', 'groupdefault.png')
+        return tools.image_resize_image_big(open(image_path, 'rb').read().encode('base64'))
+
     def _get_menu_parent(self, cr, uid, context=None):
         ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mail', 'mail_group_root')
         return ref and ref[1] or False
@@ -114,29 +96,23 @@ class mail_group(osv.Model):
     _defaults = {
         'public': 'groups',
         'group_public_id': _get_default_employee_group,
-        'responsible_id': (lambda s, cr, uid, ctx: uid),
         'image': _get_default_image,
         'parent_id': _get_menu_parent,
-        'alias_domain': False, # always hide alias during creation 
+        'alias_domain': False, # always hide alias during creation
     }
 
-    def _subscribe_user_with_group_m2m_command(self, cr, uid, ids, group_ids_command, context=None):
-        # form: {'group_ids': [(3, 10), (3, 3), (4, 10), (4, 3)]} or {'group_ids': [(6, 0, [ids]}
-        user_group_ids = [command[1] for command in group_ids_command if command[0] == 4]
-        user_group_ids += [id for command in group_ids_command if command[0] == 6 for id in command[2]]
-        # retrieve the user member of those groups
-        user_ids = []
-        res_groups_obj = self.pool.get('res.groups')
-        for group in res_groups_obj.browse(cr, uid, user_group_ids, context=context):
-            user_ids += [user.id for user in group.users]
-        # subscribe the users
-        return self.message_subscribe(cr, uid, ids, user_ids, context=context)
+    def _subscribe_users(self, cr, uid, ids, context=None):
+        for mail_group in self.browse(cr, uid, ids, context=context):
+            partner_ids = []
+            for group in mail_group.group_ids:
+                partner_ids += [user.partner_id.id for user in group.users]
+            self.message_subscribe(cr, uid, ids, partner_ids, context=context)
 
     def create(self, cr, uid, vals, context=None):
         mail_alias = self.pool.get('mail.alias')
         if not vals.get('alias_id'):
             vals.pop('alias_name', None) # prevent errors during copy()
-            alias_id = mail_alias.create_unique_alias(cr, uid, 
+            alias_id = mail_alias.create_unique_alias(cr, uid,
                           # Using '+' allows using subaddressing for those who don't
                           # have a catchall domain setup.
                           {'alias_name': "group+"+vals['name']},
@@ -148,23 +124,22 @@ class mail_group(osv.Model):
         # Create client action for this group and link the menu to it
         ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mail', 'action_mail_group_feeds')
         if ref:
-            search_ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mail', 'view_message_search_wall')
+            search_ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mail', 'view_message_search')
             params = {
                 'search_view_id': search_ref and search_ref[1] or False,
-                'domain': [('model','=','mail.group'),('res_id','=',mail_group_id)],
-                'res_model': 'mail.group',
-                'res_id': mail_group_id,
-                'thread_level': 2
+                'domain': [('model','=','mail.group'), ('res_id','=',mail_group_id)],
+                'context': {'default_model': 'mail.group', 'default_res_id': mail_group_id},
+                'res_model': 'mail.message',
+                'thread_level': 1,
             }
             cobj = self.pool.get('ir.actions.client')
             newref = cobj.copy(cr, uid, ref[1], default={'params': str(params), 'name': vals['name']}, context=context)
             self.write(cr, uid, [mail_group_id], {'action': 'ir.actions.client,'+str(newref), 'mail_group_id': mail_group_id}, context=context)
 
         mail_alias.write(cr, uid, [vals['alias_id']], {"alias_force_thread_id": mail_group_id}, context)
-       
-        if vals.get('group_ids'):
-            self._subscribe_user_with_group_m2m_command(cr, uid, [mail_group_id], vals.get('group_ids'), context=context)
 
+        if vals.get('group_ids'):
+            self._subscribe_users(cr, uid, [mail_group_id], context=context)
         return mail_group_id
 
     def unlink(self, cr, uid, ids, context=None):
@@ -176,21 +151,17 @@ class mail_group(osv.Model):
         return res
 
     def write(self, cr, uid, ids, vals, context=None):
+        result = super(mail_group, self).write(cr, uid, ids, vals, context=context)
         if vals.get('group_ids'):
-            self._subscribe_user_with_group_m2m_command(cr, uid, ids, vals.get('group_ids'), context=context)
-        return super(mail_group, self).write(cr, uid, ids, vals, context=context)
-
-    def action_group_join(self, cr, uid, ids, context=None):
-        return self.message_subscribe(cr, uid, ids, context=context)
-
-    def action_group_leave(self, cr, uid, ids, context=None):
-        return self.message_unsubscribe(cr, uid, ids, context=context)
+            self._subscribe_users(cr, uid, ids, vals.get('group_ids'), context=context)
+        return result
 
-    # ----------------------------------------
-    # OpenChatter methods and notifications
-    # ----------------------------------------
+    def action_follow(self, cr, uid, ids, context=None):
+        """ Wrapper because message_subscribe_users take a user_ids=None
+            that receive the context without the wrapper. """
+        return self.message_subscribe_users(cr, uid, ids, context=context)
 
-    def message_get_monitored_follower_fields(self, cr, uid, ids, context=None):
-        """ Add 'responsible_id' to the monitored fields """
-        res = super(mail_group, self).message_get_monitored_follower_fields(cr, uid, ids, context=context)
-        return res + ['responsible_id']
+    def action_unfollow(self, cr, uid, ids, context=None):
+        """ Wrapper because message_unsubscribe_users take a user_ids=None
+            that receive the context without the wrapper. """
+        return self.message_unsubscribe_users(cr, uid, ids, context=context)
index 8625285..25b85bb 100644 (file)
@@ -40,11 +40,12 @@ class ir_ui_menu(osv.osv):
         """ Override to take off menu entries (mail.group) the user is not
             following. """
         ids = super(ir_ui_menu, self).search(cr, uid, args, offset=0, limit=None, order=order, context=context, count=False)
+        partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
         follower_obj = self.pool.get('mail.followers')
         for menu in self.browse(cr, uid, ids, context=context):
             if menu.mail_group_id:
                 sub_ids = follower_obj.search(cr, uid, [
-                    ('user_id', '=', uid), ('res_model', '=', 'mail.group'),
+                    ('partner_id', '=', partner_id), ('res_model', '=', 'mail.group'),
                     ('res_id', '=', menu.mail_group_id.id)
                     ], context=context)
                 if not sub_ids:
index 2b5e9b3..31beba8 100644 (file)
@@ -1,10 +1,11 @@
 <?xml version="1.0"?>
 <openerp>
     <data>
+        <!-- this record will be dynamically adapted to each new menu created -->
         <record id="action_mail_group_feeds" model="ir.actions.client">
             <field name="name">Discussion Group</field>
             <field name="tag">mail.wall</field>
-            <field name="params" eval="{'search_view_id': ref('view_message_search_wall')}"/>
+            <field name="res_model">mail.message</field>
         </record>
 
         <!-- Group Kanban View  !-->
@@ -14,8 +15,9 @@
             <field name="priority" eval="10"/>
             <field name="arch" type="xml">
                 <kanban>
-                    <field name="message_follower_ids"/>
                     <field name="message_is_follower"/>
+                    <field name="message_follower_ids"/>
+                    <field name="message_summary"/>
                     <templates>
                         <t t-name="kanban-description">
                             <div class="oe_group_description" t-if="record.description.raw_value">
                                 <div class="oe_group_details">
                                     <h4><a type="open"><field name="name"/></a></h4>
                                     <ul>
-                                        <!-- <li><field name="message_follower_count"/> member(s)</li> -->
-                                        <li>
-                                            <t t-raw="record.message_follower_ids.raw_value.length"/> member(s)
-                                        </li>
-                                        <li t-if="! record.message_is_follower.raw_value"><a name="action_group_join" string="Join" type="object" class="oe_group_join">Not following</a></li>
-                                        <li t-if="record.message_is_follower.raw_value"><a name="action_group_leave" string="Join" type="object" class="oe_group_leave">Following</a></li>
-                                        <li><field name="last_month_msg_nbr"/> messages last month</li>
+                                        <li><t t-raw="record.message_summary.raw_value"/></li>
+                                        <li t-if="! record.message_is_follower.raw_value"><a name="action_follow" string="Join" type="object" class="oe_group_join">Not following</a></li>
+                                        <li t-if="record.message_is_follower.raw_value"><a name="action_unfollow" string="Leave" type="object" class="oe_group_leave">Following</a></li>
                                     </ul>
                                 </div>
                             </div>
                                     />
                                 <field name="group_ids" widget="many2many_tags" class="oe_inline"/>
                             </group>
-                            <group class="oe_edit_only">
-                                <field name="responsible_id" class="oe_inline"/>
-                            </group>
                         </group>
                     </sheet>
                     <div class="oe_chatter">
                         <field name="message_ids" widget="mail_thread"
                                 options='{"thread_level": 1}'/>
-                        <field name="message_follower_ids" widget="mail_followers"
-                                context="{'lapin': 'nouille'}" image="image_small"/>
+                        <field name="message_follower_ids" widget="mail_followers"/>
                     </div>
                 </form>
             </field>
             <field name="arch" type="xml">
                 <tree string="Groups">
                     <field name="name" string="Group Name"/>
-                    <field name="responsible_id"/>
                 </tree>
             </field>
         </record>
             <field name="arch" type="xml">
                 <search string="Search Groups">
                     <field name="name" string="Group"/>
-                    <field name="responsible_id"/>
                 </search>
             </field>
         </record>
diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py
new file mode 100644 (file)
index 0000000..7b593c5
--- /dev/null
@@ -0,0 +1,204 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2010-today OpenERP SA (<http://www.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 ast
+import base64
+import logging
+import tools
+
+from osv import osv
+from osv import fields
+from tools.translate import _
+
+_logger = logging.getLogger(__name__)
+
+class mail_mail(osv.Model):
+    """ Model holding RFC2822 email messages to send. This model also provides
+        facilities to queue and send new email messages.  """
+    _name = 'mail.mail'
+    _description = 'Outgoing Mails'
+    _inherits = {'mail.message': 'mail_message_id'}
+    _order = 'id desc'
+
+    _columns = {
+        'mail_message_id': fields.many2one('mail.message', 'Message', required=True, ondelete='cascade'),
+        'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing mail server', readonly=1),
+        'state': fields.selection([
+            ('outgoing', 'Outgoing'),
+            ('sent', 'Sent'),
+            ('received', 'Received'),
+            ('exception', 'Delivery Failed'),
+            ('cancel', 'Cancelled'),
+        ], 'Status', readonly=True),
+        'auto_delete': fields.boolean('Auto Delete',
+            help="Permanently delete this email after sending it, to save space"),
+        'references': fields.text('References', help='Message references, such as identifiers of previous messages', readonly=1),
+        'email_from': fields.char('From', help='Message sender, taken from user preferences.'),
+        'email_to': fields.text('To', help='Message recipients'),
+        'email_cc': fields.char('Cc', help='Carbon copy message recipients'),
+        'reply_to': fields.char('Reply-To', help='Preferred response address for the message'),
+        'body_html': fields.text('Rich-text Contents', help="Rich-text/HTML message"),
+
+        # Auto-detected based on create() - if 'mail_message_id' was passed then this mail is a notification
+        # and during unlink() we will cascade delete the parent and its attachments 
+        'notification': fields.boolean('Is Notification') 
+    }
+
+    def _get_default_from(self, cr, uid, context=None):
+        cur = self.pool.get('res.users').browse(cr, uid, uid, context=context)
+        if not cur.alias_domain:
+            raise osv.except_osv(_('Invalid Action!'), _('Unable to send email, set an alias domain in your server settings.'))
+        return cur.alias_name + '@' + cur.alias_domain
+
+    _defaults = {
+        'state': 'outgoing',
+        'email_from': lambda self, cr, uid, ctx=None: self._get_default_from(cr, uid, ctx),
+    }
+
+    def create(self, cr, uid, values, context=None):
+        if 'notification' not in values and values.get('mail_message_id'):
+            values['notification'] = True
+        return super(mail_mail,self).create(cr, uid, values, context=context)
+
+    def unlink(self, cr, uid, ids, context=None):
+        # cascade-delete the parent message for all mails that are not created for a notification
+        ids_to_cascade = self.search(cr, uid, [('notification','=',False),('id','in',ids)])
+        parent_msg_ids = [m.mail_message_id.id for m in self.browse(cr, uid, ids_to_cascade, context=context)]
+        res = super(mail_mail,self).unlink(cr, uid, ids, context=context)
+        self.pool.get('mail.message').unlink(cr, uid, parent_msg_ids, context=context)
+        return res
+
+    def mark_outgoing(self, cr, uid, ids, context=None):
+        return self.write(cr, uid, ids, {'state':'outgoing'}, context=context)
+
+    def cancel(self, cr, uid, ids, context=None):
+        return self.write(cr, uid, ids, {'state':'cancel'}, context=context)
+
+    def process_email_queue(self, cr, uid, ids=None, context=None):
+        """Send immediately queued messages, committing after each
+           message is sent - this is not transactional and should
+           not be called during another transaction!
+
+           :param list ids: optional list of emails ids to send. If passed
+                            no search is performed, and these ids are used
+                            instead.
+           :param dict context: if a 'filters' key is present in context,
+                                this value will be used as an additional
+                                filter to further restrict the outgoing
+                                messages to send (by default all 'outgoing'
+                                messages are sent).
+        """
+        if context is None:
+            context = {}
+        if not ids:
+            filters = ['&', ('state', '=', 'outgoing'), ('type', '=', 'email')]
+            if 'filters' in context:
+                filters.extend(context['filters'])
+            ids = self.search(cr, uid, filters, context=context)
+        res = None
+        try:
+            # Force auto-commit - this is meant to be called by
+            # the scheduler, and we can't allow rolling back the status
+            # of previously sent emails!
+            res = self.send(cr, uid, ids, auto_commit=True, context=context)
+        except Exception:
+            _logger.exception("Failed processing mail queue")
+        return res
+
+    def _postprocess_sent_message(self, cr, uid, mail, context=None):
+        """Perform any post-processing necessary after sending ``mail``
+        successfully, including deleting it completely along with its
+        attachment if the ``auto_delete`` flag of the mail was set.
+        Overridden by subclasses for extra post-processing behaviors. 
+
+        :param browse_record mail: the mail that was just sent
+        :return: True
+        """
+        if mail.auto_delete:
+            mail.unlink()
+        return True
+
+    def _send_get_mail_subject(self, cr, uid, mail, force=False, context=None):
+        """ if void and related document: '<Author> posted on <Resource>'
+            :param mail: mail.mail browse_record """
+        if force or (not mail.subject and mail.model and mail.res_id):
+            return '%s posted on %s' % (mail.author_id.name, mail.record_name)
+        return mail.subject
+
+    def send(self, cr, uid, ids, auto_commit=False, context=None):
+        """ Sends the selected emails immediately, ignoring their current
+            state (mails that have already been sent should not be passed
+            unless they should actually be re-sent).
+            Emails successfully delivered are marked as 'sent', and those
+            that fail to be deliver are marked as 'exception', and the
+            corresponding error mail is output in the server logs.
+
+            :param bool auto_commit: whether to force a commit of the mail status
+                after sending each mail (meant only for scheduler processing);
+                should never be True during normal transactions (default: False)
+            :return: True
+        """
+        ir_mail_server = self.pool.get('ir.mail_server')
+        for mail in self.browse(cr, uid, ids, context=context):
+            try:
+                body = mail.body_html
+                subject = self._send_get_mail_subject(cr, uid, mail, context=context)
+
+                # handle attachments
+                attachments = []
+                for attach in mail.attachment_ids:
+                    attachments.append((attach.datas_fname, base64.b64decode(attach.datas)))
+
+                # use only sanitized html and set its plaintexted version as alternative
+                body_alternative = tools.html2plaintext(body)
+                content_subtype_alternative = 'plain'
+
+                # build an RFC2822 email.message.Message object and send it without queuing
+                msg = ir_mail_server.build_email(
+                    email_from = mail.email_from,
+                    email_to = tools.email_split(mail.email_to),
+                    subject = subject,
+                    body = body,
+                    body_alternative = body_alternative,
+                    email_cc = tools.email_split(mail.email_cc),
+                    reply_to = mail.reply_to,
+                    attachments = attachments,
+                    message_id = mail.message_id,
+                    references = mail.references,
+                    object_id = mail.res_id and ('%s-%s' % (mail.res_id, mail.model)),
+                    subtype = 'html',
+                    subtype_alternative = content_subtype_alternative)
+                res = ir_mail_server.send_email(cr, uid, msg,
+                    mail_server_id=mail.mail_server_id.id, context=context)
+                if res:
+                    mail.write({'state':'sent', 'message_id': res})
+                else:
+                    mail.write({'state':'exception'})
+                mail.refresh()
+                if mail.state == 'sent':
+                    self._postprocess_sent_message(cr, uid, mail, context=context)
+            except Exception:
+                _logger.exception('failed sending mail.mail %s', mail.id)
+                mail.write({'state':'exception'})
+
+            if auto_commit == True:
+                cr.commit()
+        return True
diff --git a/addons/mail/mail_mail_view.xml b/addons/mail/mail_mail_view.xml
new file mode 100644 (file)
index 0000000..72d41e3
--- /dev/null
@@ -0,0 +1,127 @@
+<?xml version="1.0"?>
+<openerp>
+    <data>
+        <record model="ir.ui.view" id="view_mail_form">
+            <field name="name">mail.mail.form</field>
+            <field name="model">mail.mail</field>
+            <field name="arch" type="xml">
+                <form string="Email message" version="7.0">
+                    <sheet>
+                        <label for="subject" class="oe_edit_only"/>
+                        <h2><field name="subject"/></h2>
+                        <div>
+                            by <field name="author_id" class="oe_inline" string="User"/> on <field name="date" class="oe_inline"/>
+                            <button name="%(action_email_compose_message_wizard)d" string="Reply" type="action" icon="terp-mail-replied"
+                                context="{'default_composition_mode':'reply', 'default_parent_id': active_id}" states='received,sent,exception,cancel'/>
+                        </div>
+                        <notebook colspan="4">
+                            <page string="Message Details">
+                                <group>
+                                    <group>
+                                        <field name="email_from"/>
+                                        <field name="email_to"/>
+                                        <field name="email_cc"/>
+                                        <field name="reply_to"/>
+                                    </group>
+                                    <group>
+                                        <field name="partner_ids" widget="many2many_tags"/>
+                                    </group>
+                                </group>
+                                <notebook>
+                                    <page string="Body">
+                                        <field name="body_html"/>
+                                    </page>
+                                </notebook>
+                            </page>
+                            <page string="Advanced" groups="base.group_no_one">
+                                <group>
+                                    <group>
+                                        <field name="auto_delete"/>
+                                        <field name="type"/>
+                                        <field name="state" colspan="2"/>
+                                        <field name="mail_server_id"/>
+                                        <field name="model"/>
+                                        <field name="res_id"/>
+                                    </group>
+                                    <group>
+                                        <field name="message_id"/>
+                                        <field name="references"/>
+                                    </group>
+                                </group>
+                            </page>
+                            <page string="Attachments">
+                                <field name="attachment_ids"/>
+                            </page>
+                        </notebook>
+                    </sheet>
+                </form>
+            </field>
+        </record>
+
+        <record model="ir.ui.view" id="view_mail_tree">
+            <field name="name">mail.mail.tree</field>
+            <field name="model">mail.mail</field>
+            <field name="arch" type="xml">
+                <tree string="Emails" colors="grey:state in ('sent', 'cancel');blue:state=='outgoing';red:state=='exception';black:state=='received'">
+                    <field name="date"/>
+                    <field name="subject"/>
+                    <field name="author_id" string="User"/>
+                    <field name="message_id" invisible="1"/>
+                    <field name="partner_ids" invisible="1"/>
+                    <field name="model" invisible="1"/>
+                    <field name="res_id" invisible="1"/>
+                    <field name="email_from" invisible="1"/>
+                    <field name="state" invisible="1"/>
+                    <field name="type" invisible="1"/>
+                    <button name="send" string="Send Now" type="object" icon="gtk-media-play" states='outgoing'/>
+                    <button name="mark_outgoing" string="Retry" type="object" icon="gtk-redo" states='exception,cancel'/>
+                    <button name="cancel" string="Cancel" type="object" icon="terp-gtk-stop" states='outgoing'/>
+                </tree>
+            </field>
+        </record>
+
+        <record model="ir.ui.view" id="view_mail_search">
+            <field name="name">mail.mail.search</field>
+            <field name="model">mail.mail</field>
+            <field name="arch" type="xml">
+                <search string="Email Search">
+                    <field name="email_from" filter_domain="['|' '|',('email_from','ilike',self), ('email_to','ilike',self), ('subject','ilike',self)]" string="Email"/>
+                    <field name="date"/>
+                    <filter icon="terp-camera_test" name="received" string="Received" domain="[('state','=','received')]"/>
+                    <filter icon="terp-call-start" name="outgoing" string="Outgoing" domain="[('state','=','outgoing')]"/>
+                    <filter icon="terp-check" name="sent" string="Sent" domain="[('state','=','sent')]"/>
+                    <filter icon="terp-gtk-stop" name="exception" string="Failed" domain="[('state','=','exception')]"/>
+                    <separator/>
+                    <filter icon="terp-camera_test" name="type_email" string="Email" domain="[('type','=','email')]"/>
+                    <filter icon="terp-camera_test" name="type_comment" string="Comment" domain="[('type','=','comment')]"/>
+                    <filter icon="terp-camera_test" name="type_notification" string="Notification" domain="[('type','=','notification')]"/>
+                    <group expand="0" string="Extended Filters...">
+                        <field name="author_id"/>
+                        <field name="partner_ids"/>
+                        <field name="model"/>
+                        <field name="res_id"/>
+                    </group>
+                    <group expand="0" string="Group By...">
+                        <filter string="Status" icon="terp-stock_effects-object-colorize" domain="[]" context="{'group_by':'state'}"/>
+                        <filter string="Partner" icon="terp-partner" domain="[]" context="{'group_by':'partner_id'}"/>
+                        <filter string="User" name="User" icon="terp-personal" context="{'group_by':'user_id'}"/>
+                        <filter string="Thread" icon="terp-mail-" domain="[]" context="{'group_by':'message_id'}"/>
+                        <filter string="Month" help="Creation Month" icon="terp-go-month" domain="[]" context="{'group_by':'date'}"/>
+                    </group>
+                </search>
+            </field>
+        </record>
+
+        <record id="action_view_mail_mail" model="ir.actions.act_window">
+            <field name="name">Emails</field>
+            <field name="res_model">mail.mail</field>
+            <field name="view_type">form</field>
+            <field name="view_mode">tree,form</field>
+            <field name="context">{'search_default_outgoing': 1, 'search_default_type_email': 1}</field>
+            <field name="search_view_id" ref="view_mail_search"/>
+        </record>
+
+        <!-- Add menu entry in Settings/Email -->
+        <menuitem name="Emails" id="menu_mail_mail" parent="base.menu_email" action="action_view_mail_mail" />
+    </data>
+</openerp>
index 7801a9b..e4862ca 100644 (file)
 #
 ##############################################################################
 
-import ast
-import base64
-import dateutil.parser
-import email
 import logging
-import re
-import time
-import datetime
-from email.header import decode_header
-from email.message import Message
-
-from openerp import SUPERUSER_ID
-from osv import osv
-from osv import fields
-import pytz
-from tools import DEFAULT_SERVER_DATETIME_FORMAT
-from tools.translate import _
 import tools
 
+from email.header import decode_header
+from operator import itemgetter
+from osv import osv, fields
+
 _logger = logging.getLogger(__name__)
 
 """ Some tools for parsing / creating email fields """
@@ -47,633 +35,309 @@ def decode(text):
         text = decode_header(text.replace('\r', ''))
         return ''.join([tools.ustr(x[0], x[1]) for x in text])
 
-def mail_tools_to_email(text):
-    """Return a list of the email addresses found in ``text``"""
-    if not text: return []
-    return re.findall(r'([^ ,<@]+@[^> ,]+)', text)
+class mail_message(osv.Model):
+    """ Messages model: system notification (replacing res.log notifications),
+        comments (OpenChatter discussion) and incoming emails. """
+    _name = 'mail.message'
+    _description = 'Message'
+    _inherit = ['ir.needaction_mixin']
+    _order = 'id desc'
 
-# TODO: remove that after cleaning
-def to_email(text):
-    return mail_tools_to_email(text)
+    _message_read_limit = 10
+    _message_record_name_length = 18
 
-class mail_message_common(osv.TransientModel):
-    """ Common abstract class for holding the main attributes of a 
-        message object. It could be reused as parent model for any
-        database model or wizard screen that needs to hold a kind of
-        message.
-        All internal logic should be in another model while this
-        model holds the basics of a message. For example, a wizard for writing
-        emails should inherit from this class and not from mail.message."""
+    def _shorten_name(self, name):
+        if len(name) <= (self._message_record_name_length + 3):
+            return name
+        return name[:self._message_record_name_length] + '...'
 
-    def get_body(self, cr, uid, ids, name, arg, context=None):
-        """ get correct body version: body_html for html messages, and
-            body_text for plain text messages
-        """
-        result = dict.fromkeys(ids, '')
-        for message in self.browse(cr, uid, ids, context=context):
-            if message.content_subtype == 'html':
-                result[message.id] = message.body_html
-            else:
-                result[message.id] = message.body_text
-        return result
-    
-    def search_body(self, cr, uid, obj, name, args, context=None):
-        # will receive:
-        #   - obj: mail.message object
-        #   - name: 'body'
-        #   - args: [('body', 'ilike', 'blah')]
-        return ['|', '&', ('content_subtype', '=', 'html'), ('body_html', args[0][1], args[0][2]), ('body_text', args[0][1], args[0][2])]
-    
-    def get_record_name(self, cr, uid, ids, name, arg, context=None):
+    def _get_record_name(self, cr, uid, ids, name, arg, context=None):
+        """ Return the related document name, using get_name. """
         result = dict.fromkeys(ids, '')
         for message in self.browse(cr, uid, ids, context=context):
             if not message.model or not message.res_id:
                 continue
-            result[message.id] = self.pool.get(message.model).name_get(cr, uid, [message.res_id], context=context)[0][1]
+            result[message.id] = self._shorten_name(self.pool.get(message.model).name_get(cr, uid, [message.res_id], context=context)[0][1])
         return result
 
+    def _get_unread(self, cr, uid, ids, name, arg, context=None):
+        """ Compute if the message is unread by the current user. """
+        res = dict((id, {'unread': False}) for id in ids)
+        partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
+        notif_obj = self.pool.get('mail.notification')
+        notif_ids = notif_obj.search(cr, uid, [
+            ('partner_id', 'in', [partner_id]),
+            ('message_id', 'in', ids),
+            ('read', '=', False)
+        ], context=context)
+        for notif in notif_obj.browse(cr, uid, notif_ids, context=context):
+            res[notif.message_id.id]['unread'] = True
+        return res
+
+    def _search_unread(self, cr, uid, obj, name, domain, context=None):
+        """ Search for messages unread by the current user. Condition is
+            inversed because we search unread message on a read column. """
+        if domain[0][2]:
+            read_cond = '(read = false or read is null)'
+        else:
+            read_cond = 'read = true'
+        partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
+        cr.execute("SELECT message_id FROM mail_notification "\
+                        "WHERE partner_id = %%s AND %s" % read_cond,
+                    (partner_id,))
+        return [('id', 'in', [r[0] for r in cr.fetchall()])]
+
     def name_get(self, cr, uid, ids, context=None):
         # name_get may receive int id instead of an id list
         if isinstance(ids, (int, long)):
             ids = [ids]
         res = []
         for message in self.browse(cr, uid, ids, context=context):
-            name = ''
-            if message.subject:
-                name = '%s: ' % (message.subject)
-            if message.body_text:
-                name = '%s%s ' % (name, message.body_text[0:20])
-            if message.date:
-                name = '%s(%s)' % (name, message.date)
-            res.append((message.id, name))
+            name = '%s: %s' % (message.subject or '', message.body or '')
+            res.append((message.id, self._shorten_name(name.lstrip(' :'))))
         return res
 
-    _name = 'mail.message.common'
-    _rec_name = 'subject'
     _columns = {
-        'subject': fields.char('Subject', size=512),
+        'type': fields.selection([
+                        ('email', 'Email'),
+                        ('comment', 'Comment'),
+                        ('notification', 'System notification'),
+                        ], 'Type',
+            help="Message type: email for email message, notification for system "\
+                 "message, comment for other messages such as user replies"),
+        'author_id': fields.many2one('res.partner', 'Author', required=True),
+        'partner_ids': fields.many2many('res.partner', 'mail_notification', 'message_id', 'partner_id', 'Recipients'),
+        'attachment_ids': fields.many2many('ir.attachment', 'message_attachment_rel',
+            'message_id', 'attachment_id', 'Attachments'),
+        'parent_id': fields.many2one('mail.message', 'Parent Message', select=True, ondelete='set null', help="Initial thread message."),
+        'child_ids': fields.one2many('mail.message', 'parent_id', 'Child Messages'),
         'model': fields.char('Related Document Model', size=128, select=1),
         'res_id': fields.integer('Related Document ID', select=1),
-        'record_name': fields.function(get_record_name, type='string',
+        'record_name': fields.function(_get_record_name, type='string',
             string='Message Record Name',
             help="Name get of the related document."),
+        'notification_ids': fields.one2many('mail.notification', 'message_id', 'Notifications'),
+        'subject': fields.char('Subject'),
         'date': fields.datetime('Date'),
-        'email_from': fields.char('From', size=128, help='Message sender, taken from user preferences.'),
-        'email_to': fields.char('To', size=256, help='Message recipients'),
-        'email_cc': fields.char('Cc', size=256, help='Carbon copy message recipients'),
-        'email_bcc': fields.char('Bcc', size=256, help='Blind carbon copy message recipients'),
-        'reply_to':fields.char('Reply-To', size=256, help='Preferred response address for the message'),
-        'headers': fields.text('Message Headers', readonly=1,
-            help="Full message headers, e.g. SMTP session headers (usually available on inbound messages only)"),
-        'message_id': fields.char('Message-Id', size=256, help='Message unique identifier', select=1, readonly=1),
-        'references': fields.text('References', help='Message references, such as identifiers of previous messages', readonly=1),
-        'content_subtype': fields.char('Message content subtype', size=32,
-            oldname="subtype", readonly=1,
-            help="Type of message, usually 'html' or 'plain', used to select "\
-                  "plain-text or rich-text contents accordingly"),
-        'body_text': fields.text('Text Contents', help="Plain-text version of the message"),
-        'body_html': fields.html('Rich-text Contents', help="Rich-text/HTML version of the message"),
-        'body': fields.function(get_body, fnct_search = search_body, type='text',
-            string='Message Content', store=True,
-            help="Content of the message. This content equals the body_text field "\
-                 "for plain-test messages, and body_html for rich-text/HTML "\
-                 "messages. This allows having one field if we want to access "\
-                 "the content matching the message content_subtype."),
-        'parent_id': fields.many2one('mail.message.common', 'Parent Message',
-            select=True, ondelete='set null',
-            help="Parent message, used for displaying as threads with hierarchy"),
+        'message_id': fields.char('Message-Id', help='Message unique identifier', select=1, readonly=1),
+        'body': fields.html('Contents', help='Automatically sanitized HTML contents'),
+        'unread': fields.function(_get_unread, fnct_search=_search_unread,
+            type='boolean', string='Unread',
+            help='Functional field to search for unread messages linked to uid'),
     }
 
+    def _needaction_domain_get(self, cr, uid, context=None):
+        if self._needaction:
+            return [('unread', '=', True)]
+        return []
+
+    def _get_default_author(self, cr, uid, context=None):
+        return self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
+
     _defaults = {
-        'content_subtype': 'plain',
-        'date': (lambda *a: fields.datetime.now()),
+        'type': 'email',
+        'date': lambda *a: fields.datetime.now(),
+        'author_id': lambda self, cr, uid, ctx={}: self._get_default_author(cr, uid, ctx),
+        'body': '',
     }
 
-class mail_message(osv.Model):
-    """Model holding messages: system notification (replacing res.log
-       notifications), comments (for OpenChatter feature) and
-       RFC2822 email messages. This model also provides facilities to
-       parse, queue and send new email messages. Type of messages
-       are differentiated using the 'type' column. """
-
-    _name = 'mail.message'
-    _inherit = 'mail.message.common'
-    _description = 'Mail Message (email, comment, notification)'
-    _order = 'date desc'
+    #------------------------------------------------------
+    # Message loading for web interface
+    #------------------------------------------------------
 
-    def open_document(self, cr, uid, ids, context=None):
-        """ Open the message related document. Note that only the document of
-            ids[0] will be opened.
-            TODO: how to determine the action to use ?
+    def _message_dict_get(self, cr, uid, msg, context=None):
+        """ Return a dict representation of the message browse record. """
+        attachment_ids = self.pool.get('ir.attachment').name_get(cr, uid, [x.id for x in msg.attachment_ids], context=context)
+        author_id = self.pool.get('res.partner').name_get(cr, uid, [msg.author_id.id], context=context)[0]
+        author_user_id = self.pool.get('res.users').name_get(cr, uid, [msg.author_id.user_ids[0].id], context=context)[0]
+        partner_ids = self.pool.get('res.partner').name_get(cr, uid, [x.id for x in msg.partner_ids], context=context)
+        return {
+            'id': msg.id,
+            'type': msg.type,
+            'attachment_ids': attachment_ids,
+            'body': msg.body,
+            'model': msg.model,
+            'res_id': msg.res_id,
+            'record_name': msg.record_name,
+            'subject': msg.subject,
+            'date': msg.date,
+            'author_id': author_id,
+            'author_user_id': author_user_id,
+            'partner_ids': partner_ids,
+            'child_ids': [],
+        }
+
+    def message_read_tree_flatten(self, cr, uid, messages, current_level, level, context=None):
+        """ Given a tree with several roots of following structure :
+            [   {'id': 1, 'child_ids': [
+                    {'id': 11, 'child_ids': [...] },],
+                {...}   ]
+            Flatten it to have a maximum number of levels, 0 being flat and
+            sort messages in a level according to a key of the messages.
+            Perform the flattening at leafs if above the maximum depth, then get
+            back in the tree.
+            :param context: ``sort_key``: key for sorting (id by default)
+            :param context: ``sort_reverse``: reverser order for sorting (True by default)
         """
-        action_data = False
-        if not ids:
-            return action_data
-        msg = self.browse(cr, uid, ids[0], context=context)
-        ir_act_window = self.pool.get('ir.actions.act_window')
-        action_ids = ir_act_window.search(cr, uid, [('res_model', '=', msg.model)], context=context)
-        if action_ids:
-            action_data = ir_act_window.read(cr, uid, action_ids[0], context=context)
-            action_data.update({
-                    'domain' : "[('id', '=', %d)]" % (msg.res_id),
-                    'nodestroy': True,
-                    'context': {}
-                    })
-        return action_data
-
-    def open_attachment(self, cr, uid, ids, context=None):
-        """ Open the message related attachments.
-            TODO: how to determine the action to use ?
+        def _flatten(msg_dict):
+            """ from    {'id': x, 'child_ids': [{child1}, {child2}]}
+                get     [{'id': x, 'child_ids': []}, {child1}, {child2}]
+            """
+            child_ids = msg_dict.pop('child_ids', [])
+            msg_dict['child_ids'] = []
+            return [msg_dict] + child_ids
+            # return sorted([msg_dict] + child_ids, key=itemgetter('id'), reverse=True)
+        context = context or {}
+        # Depth-first flattening
+        for message in messages:
+            if message.get('type') == 'expandable':
+                continue
+            message['child_ids'] = self.message_read_tree_flatten(cr, uid, message['child_ids'], current_level + 1, level, context=context)
+        # Flatten if above maximum depth
+        if current_level < level:
+            return_list = messages
+        else:
+            return_list = []
+            for message in messages:
+                for flat_message in _flatten(message):
+                    return_list.append(flat_message)
+        return sorted(return_list, key=itemgetter(context.get('sort_key', 'id')), reverse=context.get('sort_reverse', True))
+
+    def message_read(self, cr, uid, ids=False, domain=[], thread_level=0, limit=None, context=None):
+        """ If IDs are provided, fetch these records. Otherwise use the domain
+            to fetch the matching records.
+            After having fetched the records provided by IDs, it will fetch the
+            parents to have well-formed threads.
+            :return list: list of trees of messages
         """
-        action_data = False
+        limit = limit or self._message_read_limit
+        context = context or {}
         if not ids:
-            return action_data
-        action_pool = self.pool.get('ir.actions.act_window')
+            ids = self.search(cr, uid, domain, context=context, limit=limit)
         messages = self.browse(cr, uid, ids, context=context)
-        att_ids = [x.id for message in messages for x in message.attachment_ids]
-        action_ids = action_pool.search(cr, uid, [('res_model', '=', 'ir.attachment')], context=context)
-        if action_ids:
-            action_data = action_pool.read(cr, uid, action_ids[0], context=context)
-            action_data.update({
-                'domain': [('id', 'in', att_ids)],
-                'nodestroy': True
+
+        result = []
+        tree = {} # key: ID, value: record
+        for msg in messages:
+            if len(result) < (limit - 1):
+                record = self._message_dict_get(cr, uid, msg, context=context)
+                if thread_level and msg.parent_id:
+                    while msg.parent_id:
+                        if msg.parent_id.id in tree:
+                            record_parent = tree[msg.parent_id.id]
+                        else:
+                            record_parent = self._message_dict_get(cr, uid, msg.parent_id, context=context)
+                            if msg.parent_id.parent_id:
+                                tree[msg.parent_id.id] = record_parent
+                        if record['id'] not in [x['id'] for x in record_parent['child_ids']]:
+                            record_parent['child_ids'].append(record)
+                        record = record_parent
+                        msg = msg.parent_id
+                if msg.id not in tree:
+                    result.append(record)
+                    tree[msg.id] = record
+            else:
+                result.append({
+                    'type': 'expandable',
+                    'domain': [('id', '<=', msg.id)] + domain,
+                    'context': context,
+                    'thread_level': thread_level,  # should be improve accodting to level of records
+                    'id': -1,
                 })
-        return action_data
-    
-    _columns = {
-        'type': fields.selection([
-                        ('email', 'email'),
-                        ('comment', 'Comment'),
-                        ('notification', 'System notification'),
-                        ], 'Type',
-            help="Message type: email for email message, notification for system "\
-                  "message, comment for other messages such as user replies"),
-        'partner_id': fields.many2one('res.partner', 'Related partner',
-            help="Deprecated field. Use partner_ids instead."),
-        'partner_ids': fields.many2many('res.partner',
-            'mail_message_res_partner_rel',
-            'message_id', 'partner_id', 'Destination partners',
-            help="When sending emails through the social network composition wizard"\
-                 "you may choose to send a copy of the mail to partners."),
-        'user_id': fields.many2one('res.users', 'Related User', readonly=1),
-        'attachment_ids': fields.many2many('ir.attachment', 'message_attachment_rel',
-            'message_id', 'attachment_id', 'Attachments'),
-        'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing mail server', readonly=1),
-        'state': fields.selection([
-                        ('outgoing', 'Outgoing'),
-                        ('sent', 'Sent'),
-                        ('received', 'Received'),
-                        ('exception', 'Delivery Failed'),
-                        ('cancel', 'Cancelled'),
-                        ], 'Status', readonly=True),
-        'auto_delete': fields.boolean('Auto Delete',
-            help="Permanently delete this email after sending it, to save space"),
-        'original': fields.binary('Original', readonly=1,
-            help="Original version of the message, as it was sent on the network"),
-        'parent_id': fields.many2one('mail.message', 'Parent Message',
-            select=True, ondelete='set null',
-            help="Parent message, used for displaying as threads with hierarchy"),
-        'child_ids': fields.one2many('mail.message', 'parent_id', 'Child Messages'),
-    }
-        
-    _defaults = {
-        'type': 'email',
-        'state': 'received',
-    }
-    
+                break
+
+        # Flatten the result
+        if thread_level > 0:
+            result = self.message_read_tree_flatten(cr, uid, result, 0, thread_level, context=context)
+        return result
+
     #------------------------------------------------------
     # Email api
     #------------------------------------------------------
-    
+
     def init(self, cr):
         cr.execute("""SELECT indexname FROM pg_indexes WHERE indexname = 'mail_message_model_res_id_idx'""")
         if not cr.fetchone():
             cr.execute("""CREATE INDEX mail_message_model_res_id_idx ON mail_message (model, res_id)""")
 
-    def check(self, cr, uid, ids, mode, context=None, values=None):
-        """Restricts the access to a mail.message, according to referred model
-        """
-        if not ids:
-            return
-        res_ids = {}
+    def check_access_rule(self, cr, uid, ids, operation, context=None):
+        """ mail.message access rule check
+            - message received (a notification exists) -> ok
+            - check rules of related document if exists
+            - fallback on normal mail.message check """
         if isinstance(ids, (int, long)):
             ids = [ids]
-        cr.execute('SELECT DISTINCT model, res_id FROM mail_message WHERE id = ANY (%s)', (ids,))
-        for rmod, rid in cr.fetchall():
+
+        # check messages for which you have a notification
+        partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
+        not_obj = self.pool.get('mail.notification')
+        not_ids = not_obj.search(cr, uid, [
+            ('partner_id', '=', partner_id),
+            ('message_id', 'in', ids),
+        ], context=context)
+        notified_ids = [notification.message_id.id for notification in not_obj.browse(cr, uid, not_ids, context=context)
+            if notification.message_id.id in ids]
+
+        # check messages linked to an existing document
+        model_record_ids = {}
+        document_ids = []
+        cr.execute('SELECT DISTINCT id, model, res_id FROM mail_message WHERE id = ANY (%s)', (ids,))
+        for id, rmod, rid in cr.fetchall():
             if not (rmod and rid):
                 continue
-            res_ids.setdefault(rmod,set()).add(rid)
-        if values:
-            if 'res_model' in values and 'res_id' in values:
-                res_ids.setdefault(values['res_model'],set()).add(values['res_id'])
+            document_ids.append(id)
+            model_record_ids.setdefault(rmod, set()).add(rid)
+        for model, mids in model_record_ids.items():
+            model_obj = self.pool.get(model)
+            mids = model_obj.exists(cr, uid, mids)
+            model_obj.check_access_rights(cr, uid, operation)
+            model_obj.check_access_rule(cr, uid, mids, operation, context=context)
+
+        # fall back on classic operation for other ids
+        other_ids = set(ids).difference(set(notified_ids), set(document_ids))
+        super(mail_message, self).check_access_rule(cr, uid, other_ids, operation, context=None)
 
-        ima_obj = self.pool.get('ir.model.access')
-        for model, mids in res_ids.items():
-            # ignore mail messages that are not attached to a resource anymore when checking access rights
-            # (resource was deleted but message was not)
-            mids = self.pool.get(model).exists(cr, uid, mids)
-            ima_obj.check(cr, uid, model, mode)
-            self.pool.get(model).check_access_rule(cr, uid, mids, mode, context=context)
-    
     def create(self, cr, uid, values, context=None):
-        self.check(cr, uid, [], mode='create', context=context, values=values)
-        return super(mail_message, self).create(cr, uid, values, context)
+        if not values.get('message_id') and values.get('res_id') and values.get('model'):
+            values['message_id'] = tools.generate_tracking_message_id('%(model)s-%(res_id)s' % values)
+        newid = super(mail_message, self).create(cr, uid, values, context)
+        self.notify(cr, uid, newid, context=context)
+        return newid
 
-    def read(self, cr, uid, ids, fields_to_read=None, context=None, load='_classic_read'):
-        self.check(cr, uid, ids, 'read', context=context)
-        return super(mail_message, self).read(cr, uid, ids, fields_to_read, context, load)
+    def unlink(self, cr, uid, ids, context=None):
+        # cascade-delete attachments that are directly attached to the message (should only happen
+        # for mail.messages that act as parent for a standalone mail.mail record.
+        attachments_to_delete = []
+        for mail in self.browse(cr, uid, ids, context=context):
+            for attach in mail.attachment_ids:
+                if attach.res_model == 'mail.message' and attach.res_id == mail.id:
+                    attachments_to_delete.append(attach.id)
+        if attachments_to_delete:
+            self.pool.get('ir.attachment').unlink(cr, uid, attachments_to_delete, context=context)
+        return super(mail_message,self).unlink(cr, uid, ids, context=context)
+
+    def notify(self, cr, uid, newid, context=None):
+        """ Add the related record followers to the destination partner_ids.
+            Call mail_notification.notify to manage the email sending
+        """
+        message = self.browse(cr, uid, newid, context=context)
+        partners_to_notify = set([])
+        # add all partner_ids of the message
+        if message.partner_ids:
+            partners_to_notify |= set(partner.id for partner in message.partner_ids)
+        # add all followers and set add them in partner_ids
+        if message.model and message.res_id:
+            record = self.pool.get(message.model).browse(cr, uid, message.res_id, context=context)
+            extra_notified = set(partner.id for partner in record.message_follower_ids)
+            missing_notified = extra_notified - partners_to_notify
+            if missing_notified:
+                message.write({'partner_ids': [(4, p_id) for p_id in missing_notified]})
+            partners_to_notify |= extra_notified
+        self.pool.get('mail.notification').notify(cr, uid, list(partners_to_notify), newid, context=context)
 
     def copy(self, cr, uid, id, default=None, context=None):
         """Overridden to avoid duplicating fields that are unique to each email"""
         if default is None:
             default = {}
-        self.check(cr, uid, [id], 'read', context=context)
-        default.update(message_id=False, original=False, headers=False)
-        return super(mail_message,self).copy(cr, uid, id, default=default, context=context)
-    
-    def write(self, cr, uid, ids, vals, context=None):
-        self.check(cr, uid, ids, 'write', context=context, values=vals)
-        return super(mail_message, self).write(cr, uid, ids, vals, context)
-
-    def unlink(self, cr, uid, ids, context=None):
-        self.check(cr, uid, ids, 'unlink', context=context)
-        return super(mail_message, self).unlink(cr, uid, ids, context)
-
-    def schedule_with_attach(self, cr, uid, email_from, email_to, subject, body, model=False, type='email',
-                             email_cc=None, email_bcc=None, reply_to=False, partner_ids=None, attachments=None,
-                             message_id=False, references=False, res_id=False, content_subtype='plain',
-                             headers=None, mail_server_id=False, auto_delete=False, context=None):
-        """ Schedule sending a new email message, to be sent the next time the
-            mail scheduler runs, or the next time :meth:`process_email_queue` is
-            called explicitly.
-
-            :param string email_from: sender email address
-            :param list email_to: list of recipient addresses (to be joined with commas) 
-            :param string subject: email subject (no pre-encoding/quoting necessary)
-            :param string body: email body, according to the ``content_subtype`` 
-                (by default, plaintext). If html content_subtype is used, the
-                message will be automatically converted to plaintext and wrapped
-                in multipart/alternative.
-            :param list email_cc: optional list of string values for CC header
-                (to be joined with commas)
-            :param list email_bcc: optional list of string values for BCC header
-                (to be joined with commas)
-            :param string model: optional model name of the document this mail
-                is related to (this will also be used to generate a tracking id,
-                used to match any response related to the same document)
-            :param int res_id: optional resource identifier this mail is related
-                to (this will also be used to generate a tracking id, used to
-                match any response related to the same document)
-            :param string reply_to: optional value of Reply-To header
-            :param partner_ids: destination partner_ids
-            :param string content_subtype: optional mime content_subtype for
-                the text body (usually 'plain' or 'html'), must match the format
-                of the ``body`` parameter. Default is 'plain', making the content
-                part of the mail "text/plain".
-            :param dict attachments: map of filename to filecontents, where
-                filecontents is a string containing the bytes of the attachment
-            :param dict headers: optional map of headers to set on the outgoing
-                mail (may override the other headers, including Subject,
-                Reply-To, Message-Id, etc.)
-            :param int mail_server_id: optional id of the preferred outgoing
-                mail server for this mail
-            :param bool auto_delete: optional flag to turn on auto-deletion of
-                the message after it has been successfully sent (default to False)
-        """
-        if context is None:
-            context = {}
-        if attachments is None:
-            attachments = {}
-        if partner_ids is None:
-            partner_ids = []
-        attachment_obj = self.pool.get('ir.attachment')
-        for param in (email_to, email_cc, email_bcc):
-            if param and not isinstance(param, list):
-                param = [param]
-        msg_vals = {
-                'subject': subject,
-                'date': fields.datetime.now(),
-                'user_id': uid,
-                'model': model,
-                'res_id': res_id,
-                'type': type,
-                'body_text': body if content_subtype != 'html' else False,
-                'body_html': body if content_subtype == 'html' else False,
-                'email_from': email_from,
-                'email_to': email_to and ','.join(email_to) or '',
-                'email_cc': email_cc and ','.join(email_cc) or '',
-                'email_bcc': email_bcc and ','.join(email_bcc) or '',
-                'partner_ids': partner_ids,
-                'reply_to': reply_to,
-                'message_id': message_id,
-                'references': references,
-                'content_subtype': content_subtype,
-                'headers': headers, # serialize the dict on the fly
-                'mail_server_id': mail_server_id,
-                'state': 'outgoing',
-                'auto_delete': auto_delete
-            }
-        email_msg_id = self.create(cr, uid, msg_vals, context)
-        attachment_ids = []
-        for attachment in attachments:
-            fname, fcontent = attachment
-            attachment_data = {
-                    'name': fname,
-                    'datas_fname': fname,
-                    'datas': fcontent and fcontent.encode('base64'),
-                    'res_model': self._name,
-                    'res_id': email_msg_id,
-            }
-            if context.has_key('default_type'):
-                del context['default_type']
-            attachment_ids.append(attachment_obj.create(cr, uid, attachment_data, context))
-        if attachment_ids:
-            self.write(cr, uid, email_msg_id, { 'attachment_ids': [(6, 0, attachment_ids)]}, context=context)
-        return email_msg_id
-
-    def mark_outgoing(self, cr, uid, ids, context=None):
-        return self.write(cr, uid, ids, {'state':'outgoing'}, context=context)
-
-    def cancel(self, cr, uid, ids, context=None):
-        return self.write(cr, uid, ids, {'state':'cancel'}, context=context)
-
-    def process_email_queue(self, cr, uid, ids=None, context=None):
-        """Send immediately queued messages, committing after each
-           message is sent - this is not transactional and should
-           not be called during another transaction!
-
-           :param list ids: optional list of emails ids to send. If passed
-                            no search is performed, and these ids are used
-                            instead.
-           :param dict context: if a 'filters' key is present in context,
-                                this value will be used as an additional
-                                filter to further restrict the outgoing
-                                messages to send (by default all 'outgoing'
-                                messages are sent).
-        """
-        if context is None:
-            context = {}
-        if not ids:
-            filters = ['&', ('state', '=', 'outgoing'), ('type', '=', 'email')]
-            if 'filters' in context:
-                filters.extend(context['filters'])
-            ids = self.search(cr, uid, filters, context=context)
-        res = None
-        try:
-            # Force auto-commit - this is meant to be called by
-            # the scheduler, and we can't allow rolling back the status
-            # of previously sent emails!
-            res = self.send(cr, uid, ids, auto_commit=True, context=context)
-        except Exception:
-            _logger.exception("Failed processing mail queue")
-        return res
-
-    def parse_message(self, message, save_original=False, context=None):
-        """Parses a string or email.message.Message representing an
-           RFC-2822 email, and returns a generic dict holding the
-           message details.
-
-           :param message: the message to parse
-           :type message: email.message.Message | string | unicode
-           :param bool save_original: whether the returned dict
-               should include an ``original`` entry with the base64
-               encoded source of the message.
-           :rtype: dict
-           :return: A dict with the following structure, where each
-                    field may not be present if missing in original
-                    message::
-
-                    { 'message-id': msg_id,
-                      'subject': subject,
-                      'from': from,
-                      'to': to,
-                      'cc': cc,
-                      'headers' : { 'X-Mailer': mailer,
-                                    #.. all X- headers...
-                                  },
-                      'content_subtype': msg_mime_subtype,
-                      'body_text': plaintext_body
-                      'body_html': html_body,
-                      'attachments': [('file1', 'bytes'),
-                                       ('file2', 'bytes') }
-                       # ...
-                       'original': source_of_email,
-                    }
-        """
-        msg_txt = message
-        if isinstance(message, str):
-            msg_txt = email.message_from_string(message)
-
-        # Warning: message_from_string doesn't always work correctly on unicode,
-        # we must use utf-8 strings here :-(
-        if isinstance(message, unicode):
-            message = message.encode('utf-8')
-            msg_txt = email.message_from_string(message)
-
-        message_id = msg_txt.get('message-id', False)
-        msg = {}
-
-        if save_original:
-            # save original, we need to be able to read the original email sometimes
-            msg['original'] = message.as_string() if isinstance(message, Message) \
-                                                  else message
-            msg['original'] = base64.b64encode(msg['original']) # binary fields are b64
-
-        if not message_id:
-            # Very unusual situation, be we should be fault-tolerant here
-            message_id = time.time()
-            msg_txt['message-id'] = message_id
-            _logger.info('Parsing Message without message-id, generating a random one: %s', message_id)
-
-        msg_fields = msg_txt.keys()
-        msg['id'] = message_id
-        msg['message-id'] = message_id
-
-        if 'Subject' in msg_fields:
-            msg['subject'] = decode(msg_txt.get('Subject'))
-
-        if 'Content-Type' in msg_fields:
-            msg['content-type'] = msg_txt.get('Content-Type')
-
-        if 'From' in msg_fields:
-            msg['from'] = decode(msg_txt.get('From') or msg_txt.get_unixfrom())
-
-        if 'To' in msg_fields:
-            msg['to'] = decode(msg_txt.get('To'))
-
-        if 'Delivered-To' in msg_fields:
-            msg['to'] = decode(msg_txt.get('Delivered-To'))
-
-        if 'CC' in msg_fields:
-            msg['cc'] = decode(msg_txt.get('CC'))
-
-        if 'Cc' in msg_fields:
-            msg['cc'] = decode(msg_txt.get('Cc'))
-
-        if 'Reply-To' in msg_fields:
-            msg['reply'] = decode(msg_txt.get('Reply-To'))
-
-        if 'Date' in msg_fields:
-            date_hdr = decode(msg_txt.get('Date'))
-            # convert from email timezone to server timezone
-            date_server_datetime = dateutil.parser.parse(date_hdr).astimezone(pytz.timezone(tools.get_server_timezone()))
-            date_server_datetime_str = date_server_datetime.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
-            msg['date'] = date_server_datetime_str
-
-        if 'Content-Transfer-Encoding' in msg_fields:
-            msg['encoding'] = msg_txt.get('Content-Transfer-Encoding')
-
-        if 'References' in msg_fields:
-            msg['references'] = msg_txt.get('References')
-
-        if 'In-Reply-To' in msg_fields:
-            msg['in-reply-to'] = msg_txt.get('In-Reply-To')
-
-        msg['headers'] = {}
-        msg['content_subtype'] = 'plain'
-        for item in msg_txt.items():
-            if item[0].startswith('X-'):
-                msg['headers'].update({item[0]: item[1]})
-        if not msg_txt.is_multipart() or 'text/plain' in msg.get('content-type', ''):
-            encoding = msg_txt.get_content_charset()
-            body = msg_txt.get_payload(decode=True)
-            if 'text/html' in msg.get('content-type', ''):
-                msg['body_html'] =  body
-                msg['content_subtype'] = 'html'
-                if body:
-                    body = tools.html2plaintext(body)
-            msg['body_text'] = tools.ustr(body, encoding)
-
-        attachments = []
-        if msg_txt.is_multipart() or 'multipart/alternative' in msg.get('content-type', ''):
-            body = ""
-            if 'multipart/alternative' in msg.get('content-type', ''):
-                msg['content_subtype'] = 'alternative'
-            else:
-                msg['content_subtype'] = 'mixed'
-            for part in msg_txt.walk():
-                if part.get_content_maintype() == 'multipart':
-                    continue
-
-                encoding = part.get_content_charset()
-                filename = part.get_filename()
-                if part.get_content_maintype()=='text':
-                    content = part.get_payload(decode=True)
-                    if filename:
-                        attachments.append((filename, content))
-                    content = tools.ustr(content, encoding)
-                    if part.get_content_subtype() == 'html':
-                        msg['body_html'] = content
-                        msg['content_subtype'] = 'html' # html version prevails
-                        body = tools.ustr(tools.html2plaintext(content))
-                        body = body.replace('&#13;', '')
-                    elif part.get_content_subtype() == 'plain':
-                        body = content
-                elif part.get_content_maintype() in ('application', 'image'):
-                    if filename :
-                        attachments.append((filename,part.get_payload(decode=True)))
-                    else:
-                        res = part.get_payload(decode=True)
-                        body += tools.ustr(res, encoding)
-
-            msg['body_text'] = body
-        msg['attachments'] = attachments
-
-        # for backwards compatibility:
-        msg['body'] = msg['body_text']
-        msg['sub_type'] = msg['content_subtype'] or 'plain'
-        return msg
-
-    def _postprocess_sent_message(self, cr, uid, message, context=None):
-        """Perform any post-processing necessary after sending ``message``
-        successfully, including deleting it completely along with its
-        attachment if the ``auto_delete`` flag of the message was set.
-        Overridden by subclasses for extra post-processing behaviors. 
-
-        :param browse_record message: the message that was just sent
-        :return: True
-        """
-        if message.auto_delete:
-            self.pool.get('ir.attachment').unlink(cr, uid,
-                [x.id for x in message.attachment_ids
-                    if x.res_model == self._name and x.res_id == message.id],
-                context=context)
-            message.unlink()
-        return True
-
-    def send(self, cr, uid, ids, auto_commit=False, context=None):
-        """Sends the selected emails immediately, ignoring their current
-           state (mails that have already been sent should not be passed
-           unless they should actually be re-sent).
-           Emails successfully delivered are marked as 'sent', and those
-           that fail to be deliver are marked as 'exception', and the
-           corresponding error message is output in the server logs.
-
-           :param bool auto_commit: whether to force a commit of the message
-                                    status after sending each message (meant
-                                    only for processing by the scheduler),
-                                    should never be True during normal
-                                    transactions (default: False)
-           :return: True
-        """
-        ir_mail_server = self.pool.get('ir.mail_server')
-        self.write(cr, uid, ids, {'state': 'outgoing'}, context=context)
-        for message in self.browse(cr, uid, ids, context=context):
-            try:
-                attachments = []
-                for attach in message.attachment_ids:
-                    attachments.append((attach.datas_fname, base64.b64decode(attach.datas)))
-
-                body = message.body_html if message.content_subtype == 'html' else message.body_text
-                body_alternative = None
-                content_subtype_alternative = None
-                if message.content_subtype == 'html' and message.body_text:
-                    # we have a plain text alternative prepared, pass it to 
-                    # build_message instead of letting it build one
-                    body_alternative = message.body_text
-                    content_subtype_alternative = 'plain'
-
-                # handle destination_partners
-                partner_ids_email_to = ''
-                for partner in message.partner_ids:
-                    partner_ids_email_to += '%s ' % (partner.email or '')
-                message_email_to = '%s %s' % (partner_ids_email_to, message.email_to or '')
-
-                # build an RFC2822 email.message.Message object and send it
-                # without queuing
-                msg = ir_mail_server.build_email(
-                    email_from=message.email_from,
-                    email_to=mail_tools_to_email(message_email_to),
-                    subject=message.subject,
-                    body=body,
-                    body_alternative=body_alternative,
-                    email_cc=mail_tools_to_email(message.email_cc),
-                    email_bcc=mail_tools_to_email(message.email_bcc),
-                    reply_to=message.reply_to,
-                    attachments=attachments, message_id=message.message_id,
-                    references = message.references,
-                    object_id=message.res_id and ('%s-%s' % (message.res_id,message.model)),
-                    subtype=message.content_subtype,
-                    subtype_alternative=content_subtype_alternative,
-                    headers=message.headers and ast.literal_eval(message.headers))
-                res = ir_mail_server.send_email(cr, uid, msg,
-                                                mail_server_id=message.mail_server_id.id,
-                                                context=context)
-                if res:
-                    message.write({'state':'sent', 'message_id': res, 'email_to': message_email_to})
-                else:
-                    message.write({'state':'exception', 'email_to': message_email_to})
-                message.refresh()
-                if message.state == 'sent':
-                    self._postprocess_sent_message(cr, uid, message, context=context)
-            except Exception:
-                _logger.exception('failed sending mail.message %s', message.id)
-                message.write({'state':'exception'})
-
-            if auto_commit == True:
-                cr.commit()
-        return True
-
-
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+        default.update(message_id=False, headers=False)
+        return super(mail_message, self).copy(cr, uid, id, default=default, context=context)
index 0aa5261..914937a 100644 (file)
@@ -2,15 +2,15 @@
 <openerp>
     <data>
         <!-- mail.message tree: short view !-->
-        <record model="ir.ui.view" id="view_message_tree_short">
-            <field name="name">mail.message.tree.short</field>
+        <record model="ir.ui.view" id="view_message_tree">
+            <field name="name">mail.message.tree</field>
             <field name="model">mail.message</field>
             <field name="priority">20</field>
             <field name="arch" type="xml">
                 <tree string="Messages">
                     <field name="date"/>
                     <field name="subject"/>
-                    <field name="user_id"/>
+                    <field name="author_id"/>
                     <field name="model"/>
                     <field name="res_id"/>
                 </tree>
@@ -18,8 +18,8 @@
         </record>
 
         <!-- mail.message form: short view !-->
-        <record model="ir.ui.view" id="view_message_form_short">
-            <field name="name">mail.message.form.short</field>
+        <record model="ir.ui.view" id="view_message_form">
+            <field name="name">mail.message.form</field>
             <field name="model">mail.message</field>
             <field name="priority">20</field>
             <field name="arch" type="xml">
                         <group>
                             <group>
                                 <field name="subject"/>
-                                <field name="user_id"/>
+                                <field name="author_id"/>
                                 <field name="date"/>
                                 <field name="type"/>
-                                <field name="content_subtype"/>
                             </group>
                             <group>
                                 <field name="model"/>
                                 <field name="partner_ids" widget="many2many_tags"/>
                             </group>
                         </group>
-                        <notebook>
-                            <page string="Body (Rich)">
-                                <field name="body_html"/>
-                            </page>
-                            <page string="Body (Plain)">
-                                <field name="body_text" widget="text"/>
-                            </page>
-                        </notebook>
+                        <field name="body"/>
                     </sheet>
                 </form>
             </field>
         </record>
 
-        <!-- mail.message search: short view !-->
-        <record model="ir.ui.view" id="view_message_search_short">
-            <field name="name">mail.message.search.short</field>
-            <field name="model">mail.message</field>
-            <field name="priority">20</field>
-            <field name="arch" type="xml">
-                <search string="Messages Search">
-                    <field name="model" string="Message"/>
-                    <field name="date"/>
-                    <field name="user_id"/>
-                </search>
-            </field>
-        </record>
-
         <!-- mail.message search: wall view !-->
-        <record model="ir.ui.view" id="view_message_search_wall">
-            <field name="name">mail.message.search.wall</field>
+        <record model="ir.ui.view" id="view_message_search">
+            <field name="name">mail.message.search</field>
             <field name="model">mail.message</field>
             <field name="priority">25</field>
             <field name="arch" type="xml">
                 <search string="Messages Search">
                     <field name="subject" string="Content" filter_domain="['|', ('subject', 'ilike', self), ('body', 'ilike', self)]" />
                     <field name="type"/>
-                    <filter icon="terp-personal+" string="My Feeds"
-                            name="my_feeds" help="My Feeds"
-                            domain="[('user_id','=',uid)]"/>
+                    <field name="author_id"/>
                     <filter icon="terp-personal+" string="Comments"
                             name="comments" help="Comments"
                             domain="[('type', '=', 'comment')]"/>
                     <filter icon="terp-personal+" string="Emails"
                             name="emails" help="Emails"
                             domain="[('type', '=', 'email')]"/>
-                    <filter icon="terp-go-today" string="Today"
-                            name="today" help="Today"
-                            domain="[   ('date', '&lt;=', datetime.date.today().strftime('%%Y-%%m-%%d 23:59:59')),
-                                        ('date', '&gt;=', datetime.date.today().strftime('%%Y-%%m-%%d 00:00:00'))
-                                    ]"/>
-                    <filter icon="terp-go-week" string="This week"
-                            name="7_days" help="This week"
-                            domain="[   ('date', '&lt;=', datetime.date.today().strftime('%%Y-%%m-%%d 23:59:59')),
-                                        ('date', '&gt;=', (datetime.date.today()-datetime.timedelta(days=7)).strftime('%%Y-%%m-%%d 00:00:00'))
-                                    ]"/>
-                    <field name="user_id"/>
-                </search>
-            </field>
-        </record>
-
-        <record model="ir.ui.view" id="view_email_message_form">
-            <field name="name">mail.message.form</field>
-            <field name="model">mail.message</field>
-            <field name="arch" type="xml">
-                <form string="Email message" version="7.0">
-                    <sheet>
-                        <label for="subject" class="oe_edit_only"/>
-                        <h2><field name="subject"/></h2>
-                                               <div>
-                            by <field name="user_id" class="oe_inline" string="User"/> on <field name="date" class="oe_inline"/>
-                            <button name="%(action_email_compose_message_wizard)d" string="Reply" type="action" icon="terp-mail-replied"
-                                context="{'mail.compose.message.mode':'reply', 'message_id':active_id}" states='received,sent,exception,cancel'/>
-                        </div>
-                        <notebook colspan="4">
-                            <page string="Message Details">
-                                <group>
-                                    <group>
-                                        <field name="email_from"/>
-                                        <field name="email_to"/>
-                                        <field name="email_cc"/>
-                                        <field name="email_bcc"/>
-                                        <field name="reply_to"/>
-                                    </group>
-                                    <group>
-                                        <field name="partner_id" readonly="1"/>
-                                        <field name="partner_ids" widget="many2many_tags"/>
-                                    </group>
-                                </group>
-                                <notebook>
-                                    <page string="Body (Rich)">
-                                        <field name="body_html"/>
-                                    </page>
-                                    <page string="Body (Plain)">
-                                        <field name="body_text" widget="text"/>
-                                    </page>
-                                </notebook>
-                            </page>
-                            <page string="Advanced" groups="base.group_no_one">
-                                <group>
-                                    <group>
-                                        <field name="auto_delete"/>
-                                        <field name="type"/>
-                                        <field name="content_subtype"/>
-                                        <field name="state" colspan="2"/>
-                                        <field name="mail_server_id"/>
-                                        <field name="original"/>
-                                        <field name="model"/>
-                                        <field name="res_id"/>
-                                        <button name="open_document" string="Open" type="object" icon="gtk-jump-to" colspan="2"
-                                            attrs="{'invisible':['|', ('model', '=', ''), ('res_id', '=', False)]}"/>
-                                    </group>
-                                    <group>
-                                        <field name="message_id"/>
-                                        <field name="references"/>
-                                        <field name="headers"/>
-                                    </group>
-                                </group>
-                            </page>
-                            <page string="Attachments">
-                                <field name="attachment_ids"/>
-                            </page>
-                        </notebook>
-                    </sheet>
-                </form>
-            </field>
-        </record>
-
-        <record model="ir.ui.view" id="view_email_message_tree">
-            <field name="name">mail.message.tree</field>
-            <field name="model">mail.message</field>
-            <field name="arch" type="xml">
-                <tree string="Emails" colors="grey:state in ('sent', 'cancel');blue:state=='outgoing';red:state=='exception';black:state=='received'">
-                    <field name="date"/>
-                    <field name="subject"/>
-                    <field name="email_from"/>
-                    <field name="user_id" string="User"/>
-                    <field name="message_id" invisible="1"/>
-                    <field name="partner_id" invisible="1"/>
-                    <field name="model" invisible="1"/>
-                    <field name="res_id" invisible="1"/>
-                    <field name="state"/>
-                    <button name="send" string="Send Now" type="object" icon="gtk-media-play" states='outgoing'/>
-                    <button name="mark_outgoing" string="Retry" type="object" icon="gtk-redo" states='exception,cancel'/>
-                    <button name="cancel" string="Cancel" type="object" icon="terp-gtk-stop" states='outgoing'/>
-                    <button name="open_document" string="Open Related Document" type="object" icon="gtk-jump-to"/>
-                    <button name="open_attachment" string="Open Attachments" type="object" icon="gtk-jump-to"/>
-                </tree>
-            </field>
-        </record>
-
-        <record model="ir.ui.view" id="view_email_message_search">
-            <field name="name">mail.message.search</field>
-            <field name="model">mail.message</field>
-            <field name="arch" type="xml">
-                <search string="Email Search">
-                    <field name="email_from" filter_domain="['|' '|',('email_from','ilike',self), ('email_to','ilike',self), ('subject','ilike',self)]" string="Email"/>
-                    <field name="date"/>
-                    <filter icon="terp-camera_test" name="received" string="Received" domain="[('state','=','received')]"/>
-                    <filter icon="terp-call-start" name="outgoing" string="Outgoing" domain="[('state','=','outgoing')]"/>
-                    <filter icon="terp-check" name="sent" string="Sent" domain="[('state','=','sent')]"/>
-                    <filter icon="terp-gtk-stop" name="exception" string="Failed" domain="[('state','=','exception')]"/>
-                    <separator/>
-                    <filter icon="terp-camera_test" name="type_email" string="Email" domain="[('type','=','email')]"/>
-                    <filter icon="terp-camera_test" name="type_comment" string="Comment" domain="[('type','=','comment')]"/>
-                    <filter icon="terp-camera_test" name="type_notification" string="Notification" domain="[('type','=','notification')]"/>
-                    <group expand="0" string="Extended Filters...">
-                        <field name="user_id" string="User"/>
-                        <field name="partner_id" string="Partner Name"/>
-                        <field name="model"/>
-                        <field name="res_id"/>
-                    </group>
-                    <group expand="0" string="Group By...">
-                        <filter string="Status" icon="terp-stock_effects-object-colorize" domain="[]" context="{'group_by':'state'}"/>
-                        <filter string="Partner" icon="terp-partner" domain="[]" context="{'group_by':'partner_id'}"/>
-                        <filter string="User" name="User" icon="terp-personal" context="{'group_by':'user_id'}"/>
-                        <filter string="Thread" icon="terp-mail-" domain="[]" context="{'group_by':'message_id'}"/>
-                        <filter string="Month" help="Creation Month" icon="terp-go-month" domain="[]" context="{'group_by':'date'}"/>
-                    </group>
+                    <field name="author_id"/>
                 </search>
             </field>
         </record>
 
-        <record id="action_view_all_messages_short" model="ir.actions.act_window">
-            <field name="name">Messages</field>
-            <field name="res_model">mail.message</field>
-            <field name="view_type">form</field>
-            <field name="view_mode">tree,form</field>
-            <field name="context">{'tree_view_ref': 'mail.view_message_tree_short', 'form_view_ref': 'mail.view_message_form_short'}</field>
-            <field name="search_view_id" ref="view_message_search_short"/>
-        </record>
-
         <record id="action_view_mail_message" model="ir.actions.act_window">
             <field name="name">Messages</field>
             <field name="res_model">mail.message</field>
             <field name="view_type">form</field>
             <field name="view_mode">tree,form</field>
-            <field name="context">{'search_default_received': 1, 'search_default_type_email': 1}</field>
-            <field name="search_view_id" ref="view_email_message_search"/>
+            <field name="search_view_id" ref="view_message_search"/>
         </record>
 
         <act_window domain="[('partner_id', '=', active_id), ('email_from', '!=', False)]"
             id="act_res_partner_emails" name="Emails"
             res_model="mail.message"
             src_model="res.partner"
-            view_id="view_email_message_tree"/>
-
-        <!-- Add menu entry in Settings/Email -->
-        <menuitem name="Messages" id="menu_email_message" parent="base.menu_email" action="action_view_mail_message" />
+            view_id="view_message_tree"/>
         
         <!-- Add menu entry in Settings/Email -->
-        <menuitem name="Feeds" id="menu_email_message_all" parent="base.menu_email" action="action_view_all_messages_short" groups="base.group_no_one"/>
+        <menuitem name="Messages" id="menu_mail_message" parent="base.menu_email" action="action_view_mail_message"/>
 
         <record id="action_mail_all_feeds" model="ir.actions.client">
             <field name="name">News Feed</field>
             <field name="tag">mail.wall</field>
-            <field name="params" eval="{'search_view_id': ref('view_message_search_wall')}"/>
+            <field name="params" eval="&quot;{'domain': [('notification_ids.partner_id.user_ids', 'in', [uid])],
+                'context': {'default_model': 'res.users', 'default_res_id': uid} }&quot;"/>
         </record>
 
         <record id="action_mail_my_feeds" model="ir.actions.client">
             <field name="name">My Feeds</field>
             <field name="tag">mail.wall</field>
-            <field name="params" eval="{'search_view_id': ref('view_message_search_wall'), 'my_feeds': True}"/>
+            <field name="params" eval="&quot;{'domain': [('author_id.user_ids', 'in', [uid])],
+                'context': {'default_model': 'res.users', 'default_res_id': uid} }&quot;"/>
         </record>
     </data>
 </openerp>
index f252124..c0d6ac9 100644 (file)
 ##############################################################################
 
 import base64
+import dateutil
 import email
 import logging
-import re
+import pytz
 import time
+import tools
 import xmlrpclib
-from email.utils import parsedate
-from email.message import Message
 
+from email.message import Message
+from mail_message import decode
 from osv import osv, fields
-from mail_message import decode, to_email
-import tools
-from tools.translate import _
 from tools.safe_eval import safe_eval as eval
 
 _logger = logging.getLogger(__name__)
 
 def decode_header(message, header, separator=' '):
-    return separator.join(map(decode,message.get_all(header, [])))
+    return separator.join(map(decode, message.get_all(header, [])))
 
 class many2many_reference(fields.many2many):
-    """ many2many_reference is an override of fields.many2many. It manages
-        many2many-like table where one id is given by two fields, res_model
-        and res_id.
-    """
-    
+    """ many2many_reference manages many2many fields where one id is found
+        by a reference-like key (a char column in addition to the foreign id).
+        The reference_column attribute on the many2many fields is used;
+        if not defined, ``res_model`` is used. """
+
     def _get_query_and_where_params(self, cr, model, ids, values, where_params):
-        """ Add in where:
-            - mail_followers.res_model = 'crm.lead'
-        """
+        """ Add in where condition like mail_followers.res_model = 'crm.lead' """
+        reference_column = self.reference_column if self.reference_column else 'res_model'
+        values.update(reference_column=reference_column, reference_value=model._name)
         query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \
                     FROM %(rel)s, %(from_c)s \
                     WHERE %(rel)s.%(id1)s IN %%s \
                     AND %(rel)s.%(id2)s = %(tbl)s.id \
-                    AND %(rel)s.res_model = %%s \
+                    AND %(rel)s.%(reference_column)s = \'%(reference_value)s\' \
                     %(where_c)s  \
                     %(order_by)s \
                     %(limit)s \
                     OFFSET %(offset)d' \
                 % values
-        where_params = [model._name] + where_params
         return query, where_params
 
     def set(self, cr, model, id, name, values, user=None, context=None):
-        """ Override to add the res_model field in queries. """
+        """ Override to add the reference field in queries. """
         if not values: return
         rel, id1, id2 = self._sql_names(model)
         obj = model.pool.get(self._obj)
+        # reference column name: given by attribute or res_model
+        reference_column = self.reference_column if self.reference_column else 'res_model'
         for act in values:
             if not (isinstance(act, list) or isinstance(act, tuple)) or not act:
                 continue
             if act[0] == 0:
                 idnew = obj.create(cr, user, act[2], context=context)
-                cr.execute('INSERT INTO '+rel+' ('+id1+','+id2+',res_model) VALUES (%s,%s,%s)', (id, idnew, model._name))
+                cr.execute('INSERT INTO '+rel+' ('+id1+','+id2+','+reference_column+') VALUES (%s,%s,%s)', (id, idnew, model._name))
             elif act[0] == 3:
-                cr.execute('DELETE FROM '+rel+' WHERE '+id1+'=%s AND '+id2+'=%s AND res_model=%s', (id, act[1], model._name))
+                cr.execute('DELETE FROM '+rel+' WHERE '+id1+'=%s AND '+id2+'=%s AND '+reference_column+'=%s', (id, act[1], model._name))
             elif act[0] == 4:
                 # following queries are in the same transaction - so should be relatively safe
-                cr.execute('SELECT 1 FROM '+rel+' WHERE '+id1+'=%s AND '+id2+'=%s AND res_model=%s', (id, act[1], model._name))
+                cr.execute('SELECT 1 FROM '+rel+' WHERE '+id1+'=%s AND '+id2+'=%s AND '+reference_column+'=%s', (id, act[1], model._name))
                 if not cr.fetchone():
-                    cr.execute('INSERT INTO '+rel+' ('+id1+','+id2+',res_model) VALUES (%s,%s,%s)', (id, act[1], model._name))
+                    cr.execute('INSERT INTO '+rel+' ('+id1+','+id2+','+reference_column+') VALUES (%s,%s,%s)', (id, act[1], model._name))
+            elif act[0] == 5:
+                cr.execute('delete from '+rel+' where '+id1+' = %s AND '+reference_column+'=%s', (id, model._name))
             elif act[0] == 6:
                 d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context)
                 if d1:
                     d1 = ' and ' + ' and '.join(d1)
                 else:
                     d1 = ''
-                cr.execute('DELETE FROM '+rel+' WHERE '+id1+'=%s AND res_model=%s AND '+id2+' IN (SELECT '+rel+'.'+id2+' FROM '+rel+', '+','.join(tables)+' WHERE '+rel+'.'+id1+'=%s AND '+rel+'.'+id2+' = '+obj._table+'.id '+ d1 +')', [id, model._name, id]+d2)
+                cr.execute('DELETE FROM '+rel+' WHERE '+id1+'=%s AND '+reference_column+'=%s AND '+id2+' IN (SELECT '+rel+'.'+id2+' FROM '+rel+', '+','.join(tables)+' WHERE '+rel+'.'+id1+'=%s AND '+rel+'.'+id2+' = '+obj._table+'.id '+ d1 +')', [id, model._name, id]+d2)
                 for act_nbr in act[2]:
-                    cr.execute('INSERT INTO '+rel+' ('+id1+','+id2+',res_model) VALUES (%s,%s,%s)', (id, act_nbr, model._name))
+                    cr.execute('INSERT INTO '+rel+' ('+id1+','+id2+','+reference_column+') VALUES (%s,%s,%s)', (id, act_nbr, model._name))
+            # cases 1, 2: performs write and unlink -> default implementation is ok
             else:
                 return super(many2many_reference, self).set(cr, model, id, name, values, user, context)
 
-class mail_thread(osv.Model):
-    '''Mixin model, meant to be inherited by any model that needs to
-       act as a discussion topic on which messages can be attached.
-       Public methods are prefixed with ``message_`` in order to avoid
-       name collisions with methods of the models that will inherit
-       from this mixin.
-
-       ``mail.thread`` is designed to work without adding any field
-       to the extended models. All functionalities and expected behavior
-       are managed by mail.thread, using model name and record ids.
-       A widget has been designed for the 6.1 and following version of OpenERP
-       web-client. However, due to technical limitations, ``mail.thread``
-       adds a simulated one2many field, to display the web widget by
-       overriding the default field displayed. Using this field
-       is not recommanded has it will disappeear in future version
-       of OpenERP, leading to a pure mixin class.
-
-       Inheriting classes are not required to implement any method, as the
-       default implementation will work for any model. However it is common
-       to override at least the ``message_new`` and ``message_update``
-       methods (calling ``super``) to add model-specific behavior at
-       creation and update of a thread.
-       
-       #TODO: UPDATE WITH SUBTYPE / NEW FOLLOW MECHANISM
+class mail_thread(osv.AbstractModel):
+    ''' mail_thread model is meant to be inherited by any model that needs to
+        act as a discussion topic on which messages can be attached. Public
+        methods are prefixed with ``message_`` in order to avoid name
+        collisions with methods of the models that will inherit from this class.
+
+        ``mail.thread`` defines fields used to handle and display the
+        communication history. ``mail.thread`` also manages followers of
+        inheriting classes. All features and expected behavior are managed
+        by mail.thread. Widgets has been designed for the 7.0 and following
+        versions of OpenERP.
+
+        Inheriting classes are not required to implement any method, as the
+        default implementation will work for any model. However it is common
+        to override at least the ``message_new`` and ``message_update``
+        methods (calling ``super``) to add model-specific behavior at
+        creation and update of a thread when processing incoming emails.
     '''
     _name = 'mail.thread'
     _description = 'Email Thread'
 
-    def _get_message_data(self, cr, uid, ids, field_names, args, context=None):
-        res = dict.fromkeys(ids)
-        for id in ids:
-            res[id] = {'message_ids': self.message_search(cr, uid, [id], context=context)}
+    def _get_message_data(self, cr, uid, ids, name, args, context=None):
+        res = dict((id, dict(message_unread=False, message_summary='')) for id in ids)
+        user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
+
+        notif_obj = self.pool.get('mail.notification')
+        notif_ids = notif_obj.search(cr, uid, [
+            ('partner_id.user_ids', 'in', [uid]),
+            ('message_id.res_id', 'in', ids),
+            ('message_id.model', '=', self._name),
+            ('read', '=', False)
+        ], context=context)
+        for notif in notif_obj.browse(cr, uid, notif_ids, context=context):
+            res[notif.message_id.res_id]['message_unread'] = True
+
         for thread in self.browse(cr, uid, ids, context=context):
-            message_follower_ids = [follower.id for follower in thread.message_follower_ids]
-            res[thread.id].update({
-                'message_is_follower': uid in message_follower_ids,
-                'message_summary': "<span><span class='oe_e'>9</span> %d</span> <span><span class='oe_e'>+</span> %d</span>" % 
-                    (len(res[thread.id]['message_ids']), len(thread.message_follower_ids))
-                })
+            cls = res[thread.id]['message_unread'] and ' class="oe_kanban_mail_new"' or ''
+            res[thread.id]['message_summary'] = "<span%s><span class='oe_e'>9</span> %d</span> <span><span class='oe_e'>+</span> %d</span>" % (cls, len(thread.message_comment_ids), len(thread.message_follower_ids))
+            res[thread.id]['message_is_follower'] = user.partner_id.id in [follower.id for follower in thread.message_follower_ids]
         return res
 
-    def _search_message_ids(self, cr, uid, obj, name, args, context=None):
-        msg_obj = self.pool.get('mail.message')
-        msg_ids = msg_obj.search(cr, uid, ['&', ('res_id', 'in', args[0][2]), ('model', '=', self._name)], context=context)
-        return [('id', 'in', msg_ids)]
+    def _search_unread(self, cr, uid, obj=None, name=None, domain=None, context=None):
+        partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
+        res = {}
+        notif_obj = self.pool.get('mail.notification')
+        notif_ids = notif_obj.search(cr, uid, [
+            ('partner_id', '=', partner_id),
+            ('message_id.model', '=', self._name),
+            ('read', '=', False)
+        ], context=context)
+        for notif in notif_obj.browse(cr, uid, notif_ids, context=context):
+            res[notif.message_id.res_id] = True
+        return [('id', 'in', res.keys())]
 
     _columns = {
-        'message_ids': fields.function(_get_message_data,
-                       fnct_search=_search_message_ids,
-            type='one2many', obj='mail.message', _fields_id = 'res_id',
-            string='Messages', multi="_get_message_data",
-            help="Field holding discussion about the current document."),
-        'message_follower_ids': many2many_reference('res.users',
-            rel='mail_followers', id1='res_id', id2='user_id', string="Followers",
-            help="Followers of the document. The followers have full access to " \
-                 "the document details, as well as the conversation."),
-        'message_is_follower': fields.function(_get_message_data, method=True,
-            type='boolean', string='I am Follower', multi='_get_message_data',
-            help='True if the current user is following the current document.'),
-        'message_state': fields.boolean('Read',
-            help="When checked, new messages require your attention."),
+        'message_is_follower': fields.function(_get_message_data,
+            type='boolean', string='Is a Follower', multi='_get_message_data'),
+        'message_follower_ids': many2many_reference('res.partner',
+            'mail_followers', 'res_id', 'partner_id',
+            reference_column='res_model', string='Followers'),
+        'message_comment_ids': fields.one2many('mail.message', 'res_id',
+            domain=lambda self: [('model', '=', self._name), ('type', 'in', ('comment', 'email'))],
+            string='Comments and emails',
+            help="Comments and emails"),
+        'message_ids': fields.one2many('mail.message', 'res_id',
+            domain=lambda self: [('model', '=', self._name)],
+            string='Messages',
+            help="Messages and communication history"),
+        'message_unread': fields.function(_get_message_data, fnct_search=_search_unread,
+            type='boolean', string='Unread Messages', multi="_get_message_data",
+            help="If checked new messages require your attention."),
         'message_summary': fields.function(_get_message_data, method=True,
-            type='text', string='Summary', multi='_get_message_data',
+            type='text', string='Summary', multi="_get_message_data",
             help="Holds the Chatter summary (number of messages, ...). "\
                  "This summary is directly in html format in order to "\
                  "be inserted in kanban views."),
     }
 
-    _defaults = {
-        'message_state': True,
-    }
-
     #------------------------------------------------------
-    # Automatic subscription when creating/reading
+    # Automatic subscription when creating
     #------------------------------------------------------
 
     def create(self, cr, uid, vals, context=None):
-        """ Override of create to subscribe :
-            - the writer
-            - followers given by the monitored fields
-        """
+        """ Override to subscribe the current user. """
         thread_id = super(mail_thread, self).create(cr, uid, vals, context=context)
-        followers_command = self.message_get_automatic_followers(cr, uid, thread_id, vals, fetch_missing=False, context=context)
-        if followers_command:
-            self.write(cr, uid, [thread_id], {'message_follower_ids': followers_command}, context=context)
+        self.message_subscribe_users(cr, uid, [thread_id], [uid], context=context)
         return thread_id
 
-    def write(self, cr, uid, ids, vals, context=None):
-        """ Override of write to subscribe :
-            - the writer
-            - followers given by the monitored fields
-        """
-        if isinstance(ids, (int, long)):
-            ids = [ids]
-        for id in ids:
-            # copy original vals because we are going to modify it
-            specific_vals = dict(vals)
-            # we modify followers: do not subscribe the uid
-            if specific_vals.get('message_follower_ids'):
-                followers_command = self.message_get_automatic_followers(cr, uid, id, specific_vals, add_uid=False, context=context)
-                specific_vals['message_follower_ids'] += followers_command
-            else:
-                followers_command = self.message_get_automatic_followers(cr, uid, id, specific_vals, context=context)
-                specific_vals['message_follower_ids'] = followers_command
-            write_res = super(mail_thread, self).write(cr, uid, ids, specific_vals, context=context)
-        return True
-
     def unlink(self, cr, uid, ids, context=None):
-        """Override unlink, to automatically delete messages
-           that are linked with res_model and res_id, not through
-           a foreign key with a 'cascade' ondelete attribute.
-           Notifications will be deleted with messages
-        """
+        """ Override unlink to delete messages and followers. This cannot be
+            cascaded, because link is done through (res_model, res_id). """
         msg_obj = self.pool.get('mail.message')
+        fol_obj = self.pool.get('mail.followers')
         # delete messages and notifications
-        msg_to_del_ids = msg_obj.search(cr, uid, [('model', '=', self._name), ('res_id', 'in', ids)], context=context)
-        msg_obj.unlink(cr, uid, msg_to_del_ids, context=context)
+        msg_ids = msg_obj.search(cr, uid, [('model', '=', self._name), ('res_id', 'in', ids)], context=context)
+        msg_obj.unlink(cr, uid, msg_ids, context=context)
+        # delete followers
+        fol_ids = fol_obj.search(cr, uid, [('res_model', '=', self._name), ('res_id', 'in', ids)], context=context)
+        fol_obj.unlink(cr, uid, fol_ids, context=context)
         return super(mail_thread, self).unlink(cr, uid, ids, context=context)
 
-    def message_get_automatic_followers(self, cr, uid, id, record_vals, add_uid=True, fetch_missing=False, context=None):
-        """ Return the command for the many2many follower_ids field to manage
-            subscribers. Behavior :
-            - get the monitored fields (ex: ['user_id', 'responsible_id']); those
-              fields should be relationships to res.users (#TODO: res.partner)
-            - if this field is in the record_vals: it means it has been modified
-              thus add its value to the followers
-            - if this fields is not in record_vals, but fetch_missing paramter
-              is set to True: fetch the value in the record (use: at creation
-              for default values, not present in record_vals)
-            - if add_uid: add the current user (for example: writer is subscriber)
-            - generate the command and return it
-            This method has to be used on 1 id, because otherwise it would imply
-            to track which user.id is used for which record.id.
-
-            :param record_vals: values given to the create method of the new
-                record, or values updated in a write.
-            :param monitored_fields: a list of fields that are monitored. Those
-                fields must be many2one fields to the res.users model.
-            :param fetch_missing: is set to True, the method will read the
-                record to find values that are not present in record_vals.
-
-            #TODO : UPDATE WHEN MERGING TO PARTNERS
-        """
-        # get monitored fields
-        monitored_fields = self.message_get_monitored_follower_fields(cr, uid, [id], context=context)
-        modified_fields = [field for field in monitored_fields if field in record_vals.iterkeys()]
-        other_fields = [field for field in monitored_fields if field not in record_vals.iterkeys()] if fetch_missing else []
-        # for each monitored field: if in record_vals, it has been modified/added
-        follower_ids = []
-        for field in modified_fields:
-            # do not add 'False'
-            if record_vals.get(fields):
-                follower_ids.append(record_vals.get(field))
-        # for other fields: read in record if fetch_missing (otherwise list is void)
-        for field in other_fields:
-            record = self.browse(cr, uid, id, context=context)
-            value = getattr(record, field)
-            if value:
-                follower_ids.append(value)
-        # add uid if asked and not already present
-        if add_uid and uid not in follower_ids:
-            follower_ids.append(uid)
-        return self.message_subscribe_get_command(cr, uid, follower_ids, context=context)
-
     #------------------------------------------------------
     # mail.message wrappers and tools
     #------------------------------------------------------
 
-    def message_create(self, cr, uid, thread_id, vals, context=None):
-        """ OpenChatter: wrapper of mail.message create method
-           - creates the mail.message
-           - automatically subscribe the message writer
-           - push the message to followers
-        """
-        if context is None:
-            context = {}
-
-        # create message
-        msg_id = self.pool.get('mail.message').create(cr, uid, vals, context=context)
-
-        # automatically subscribe the writer of the message
-        if vals.get('user_id'):
-            record = self.browse(cr, uid, thread_id, context=context)
-            follower_ids = [follower.id for follower in record.message_follower_ids]
-            if vals.get('user_id') not in follower_ids:
-                self.message_subscribe(cr, uid, [thread_id], [vals.get('user_id')], context=context)
-
-        # Set as unread if writer is not the document responsible
-        self.message_create_set_unread(cr, uid, [thread_id], context=context)
-        
-        # special: if install mode, do not push demo data
-        if context.get('install_mode', False):
-            return msg_id
-        
-        # get users that will get a notification pushed
-        notification_obj = self.pool.get('mail.notification')
-        user_to_push_ids = self.message_get_user_ids_to_notify(cr, uid, [thread_id], vals, context=context)
-        for id in user_to_push_ids:
-            notification_obj.create(cr, uid, {'user_id': id, 'message_id': msg_id}, context=context)
-        
-        # create the email to send
-        self.message_create_notify_by_email(cr, uid, vals, user_to_push_ids, context=context)
-        
-        return msg_id
-
-    def message_get_user_ids_to_notify(self, cr, uid, thread_ids, new_msg_vals, context=None):
-        # get body
-        body = new_msg_vals.get('body_html', '') if new_msg_vals.get('content_subtype') == 'html' else new_msg_vals.get('body_text', '')
-        
-        # get subscribers
-        subscr_obj = self.pool.get('mail.followers')
-        subscr_ids = subscr_obj.search(cr, uid, ['&', ('res_model', '=', self._name), ('res_id', 'in', thread_ids)], context=context)
-        notif_user_ids = [sub['user_id'][0] for sub in subscr_obj.read(cr, uid, subscr_ids, ['user_id'], context=context)]
-    
-        # add users requested to perform an action (need_action mechanism)
-        if hasattr(self, 'get_needaction_user_ids') and self._columns.get('user_id'):
-            user_ids_dict = self.get_needaction_user_ids(cr, uid, thread_ids, context=context)
-            for id, user_ids in user_ids_dict.iteritems():
-                notif_user_ids += user_ids
-        
-        # add users notified of the parent messages (because: if parent message contains @login, login must receive the replies)
-        if new_msg_vals.get('parent_id'):
-            notif_obj = self.pool.get('mail.notification')
-            parent_notif_ids = notif_obj.search(cr, uid, [('message_id', '=', new_msg_vals.get('parent_id'))], context=context)
-            parent_notifs = notif_obj.read(cr, uid, parent_notif_ids, context=context)
-            notif_user_ids += [parent_notif['user_id'][0] for parent_notif in parent_notifs]
-
-        # remove duplicate entries
-        notif_user_ids = list(set(notif_user_ids))
-        return notif_user_ids
+    def _needaction_domain_get(self, cr, uid, context=None):
+        if self._needaction:
+            return [('message_unread', '=', True)]
+        return []
 
     #------------------------------------------------------
-    # Generic message api
+    # Mail gateway
     #------------------------------------------------------
 
     def message_capable_models(self, cr, uid, context=None):
+        """ Used by the plugin addon, based for plugin_outlook and others. """
         ret_dict = {}
         for model_name in self.pool.obj_list():
             model = self.pool.get(model_name)
@@ -335,343 +218,20 @@ class mail_thread(osv.Model):
                 ret_dict[model_name] = model._description
         return ret_dict
 
-    def message_append(self, cr, uid, threads, subject, body_text=None, body_html=None,
-                        type='email', email_date=None, parent_id=False,
-                        content_subtype='plain', state=None,
-                        partner_ids=None, email_from=False, email_to=False,
-                        email_cc=None, email_bcc=None, reply_to=None,
-                        headers=None, message_id=False, references=None,
-                        attachments=None, original=None, context=None):
-        """ Creates a new mail.message through message_create. The new message
-            is attached to the current mail.thread, containing all the details 
-            passed as parameters. All attachments will be attached to the 
-            thread record as well as to the actual message.
-           
-            This method calls message_create that will handle management of
-            subscription and notifications, and effectively create the message.
-           
-            If ``email_from`` is not set or ``type`` not set as 'email',
-            a note message is created (comment or system notification), 
-            without the usual envelope attributes (sender, recipients, etc.).
-
-            :param threads: list of thread ids, or list of browse_records
-                representing threads to which a new message should be attached
-            :param subject: subject of the message, or description of the event;
-                this is totally optional as subjects are not important except
-                for specific messages (blog post, job offers) or for emails
-            :param body_text: plaintext contents of the mail or log message
-            :param body_html: html contents of the mail or log message
-            :param type: type of message: 'email', 'comment', 'notification';
-                email by default
-            :param email_date: email date string if different from now, in
-                server timezone
-            :param parent_id: id of the parent message (threaded messaging model)
-            :param content_subtype: optional content_subtype of message: 'plain'
-                or 'html', corresponding to the main body contents (body_text or
-                body_html).
-            :param state: state of message
-            :param partner_ids: destination partners of the message, in addition
-                to the now fully optional email_to; this method is supposed to
-                received a list of ids is not None. The specific many2many
-                instruction will be generated by this method.
-            :param email_from: Email From / Sender address if any
-            :param email_to: Email-To / Recipient address
-            :param email_cc: Comma-Separated list of Carbon Copy Emails To
-                addresses if any
-            :param email_bcc: Comma-Separated list of Blind Carbon Copy Emails To
-                addresses if any
-            :param reply_to: reply_to header
-            :param headers: mail headers to store
-            :param message_id: optional email identifier
-            :param references: optional email references
-            :param dict attachments: map of attachment filenames to binary
-                contents, if any.
-            :param str original: optional full source of the RFC2822 email, for
-                reference
-            :param dict context: if a ``thread_model`` value is present in the
-                context, its value will be used to determine the model of the
-                thread to update (instead of the current model).
-        """
-        if context is None:
-            context = {}
-        if attachments is None:
-            attachments = {}
-
-        if email_date:
-            edate = parsedate(email_date)
-            if edate is not None:
-                email_date = time.strftime('%Y-%m-%d %H:%M:%S', edate)
-
-        if all(isinstance(thread_id, (int, long)) for thread_id in threads):
-            model = context.get('thread_model') or self._name
-            model_pool = self.pool.get(model)
-            threads = model_pool.browse(cr, uid, threads, context=context)
-
-        ir_attachment = self.pool.get('ir.attachment')
-
-        new_msg_ids = []
-        for thread in threads:
-            to_attach = []
-            for attachment in attachments:
-                fname, fcontent = attachment
-                if isinstance(fcontent, unicode):
-                    fcontent = fcontent.encode('utf-8')
-                data_attach = {
-                    'name': fname,
-                    'datas': base64.b64encode(str(fcontent)),
-                    'datas_fname': fname,
-                    'description': _('Mail attachment'),
-                    'res_model': thread._name,
-                    'res_id': thread.id,
-                }
-                to_attach.append(ir_attachment.create(cr, uid, data_attach, context=context))
-            # find related partner: partner_id column in thread object, or self is res.partner model
-            partner_id = ('partner_id' in thread._columns.keys()) and (thread.partner_id and thread.partner_id.id or False) or False
-            if not partner_id and thread._name == 'res.partner':
-                partner_id = thread.id
-            # destination partners
-            if partner_ids is None:
-                partner_ids = []
-            mail_partner_ids = [(6, 0, partner_ids)]
-
-            data = {
-                'subject': subject,
-                'body_text': body_text or thread._model._columns.get('description') and thread.description or '',
-                'body_html': body_html or '',
-                'parent_id': parent_id,
-                'date': email_date or fields.datetime.now(),
-                'type': type,
-                'content_subtype': content_subtype,
-                'state': state,
-                'message_id': message_id,
-                'partner_ids': mail_partner_ids,
-                'attachment_ids': [(6, 0, to_attach)],
-                'user_id': uid,
-                'model' : thread._name,
-                'res_id': thread.id,
-                'partner_id': partner_id,
-            }
-
-            if email_from or type == 'email':
-                for param in (email_to, email_cc, email_bcc):
-                    if isinstance(param, list):
-                        param = ", ".join(param)
-                data.update({
-                    'email_to': email_to,
-                    'email_from': email_from or \
-                        thread._model._columns.get('user_id') and thread.user_id and thread.user_id.user_email,
-                    'email_cc': email_cc,
-                    'email_bcc': email_bcc,
-                    'references': references,
-                    'headers': headers,
-                    'reply_to': reply_to,
-                    'original': original, })
-
-            new_msg_ids.append(self.message_create(cr, uid, thread.id, data, context=context))
-        return new_msg_ids
-
-    def message_append_dict(self, cr, uid, ids, msg_dict, context=None):
-        """Creates a new mail.message attached to the given threads (``ids``),
-           with the contents of ``msg_dict``, by calling ``message_append``
-           with the mail details. All attachments in msg_dict will be
-           attached to the object record as well as to the actual
-           mail message.
-
-           :param dict msg_dict: a map containing the email details and
-                                 attachments. See ``message_process()`` and
-                                ``mail.message.parse()`` for details on
-                                the dict structure.
-           :param dict context: if a ``thread_model`` value is present
-                                in the context, its value will be used
-                                to determine the model of the thread to
-                                update (instead of the current model).
-        """
-        return self.message_append(cr, uid, ids,
-                            subject = msg_dict.get('subject'),
-                            body_text = msg_dict.get('body_text'),
-                            body_html= msg_dict.get('body_html'),
-                            parent_id = msg_dict.get('parent_id', False),
-                            type = msg_dict.get('type', 'email'),
-                            content_subtype = msg_dict.get('content_subtype'),
-                            state = msg_dict.get('state'),
-                            partner_ids = msg_dict.get('partner_ids'),
-                            email_from = msg_dict.get('from', msg_dict.get('email_from')),
-                            email_to = msg_dict.get('to', msg_dict.get('email_to')),
-                            email_cc = msg_dict.get('cc', msg_dict.get('email_cc')),
-                            email_bcc = msg_dict.get('bcc', msg_dict.get('email_bcc')),
-                            reply_to = msg_dict.get('reply', msg_dict.get('reply_to')),
-                            email_date = msg_dict.get('date'),
-                            message_id = msg_dict.get('message-id', msg_dict.get('message_id')),
-                            references = msg_dict.get('references')\
-                                      or msg_dict.get('in-reply-to'),
-                            attachments = msg_dict.get('attachments'),
-                            headers = msg_dict.get('headers'),
-                            original = msg_dict.get('original'),
-                            context = context)
-
-    #------------------------------------------------------
-    # Message loading
-    #------------------------------------------------------
-
-    def _message_search_ancestor_ids(self, cr, uid, ids, child_ids, ancestor_ids, context=None):
-        """ Given message child_ids ids, find their ancestors until ancestor_ids
-            using their parent_id relationship.
-
-            :param child_ids: the first nodes of the search
-            :param ancestor_ids: list of ancestors. When the search reach an
-                                 ancestor, it stops.
-        """
-        def _get_parent_ids(message_list, ancestor_ids, child_ids):
-            """ Tool function: return the list of parent_ids of messages
-                contained in message_list. Parents that are in ancestor_ids
-                or in child_ids are not returned. """
-            return [message['parent_id'][0] for message in message_list
-                        if message['parent_id']
-                        and message['parent_id'][0] not in ancestor_ids
-                        and message['parent_id'][0] not in child_ids
-                    ]
-
-        message_obj = self.pool.get('mail.message')
-        messages_temp = message_obj.read(cr, uid, child_ids, ['id', 'parent_id'], context=context)
-        parent_ids = _get_parent_ids(messages_temp, ancestor_ids, child_ids)
-        child_ids += parent_ids
-        cur_iter = 0; max_iter = 100; # avoid infinite loop
-        while (parent_ids and (cur_iter < max_iter)):
-            cur_iter += 1
-            messages_temp = message_obj.read(cr, uid, parent_ids, ['id', 'parent_id'], context=context)
-            parent_ids = _get_parent_ids(messages_temp, ancestor_ids, child_ids)
-            child_ids += parent_ids
-        if (cur_iter > max_iter):
-            _logger.warning("Possible infinite loop in _message_search_ancestor_ids. "\
-                "Note that this algorithm is intended to check for cycle in "\
-                "message graph, leading to a curious error. Have fun.")
-        return child_ids
-
-    def message_search_get_domain(self, cr, uid, ids, context=None):
-        """ OpenChatter feature: get the domain to search the messages related
-            to a document. mail.thread defines the default behavior as
-            being messages with model = self._name, id in ids.
-            This method should be overridden if a model has to implement a
-            particular behavior.
-        """
-        return ['&', ('res_id', 'in', ids), ('model', '=', self._name)]
-
-    def message_search(self, cr, uid, ids, fetch_ancestors=False, ancestor_ids=None, 
-                        limit=100, offset=0, domain=None, count=False, context=None):
-        """ OpenChatter feature: return thread messages ids according to the
-            search domain given by ``message_search_get_domain``.
-            
-            It is possible to add in the search the parent of messages by
-            setting the fetch_ancestors flag to True. In that case, using
-            the parent_id relationship, the method returns the id list according
-            to the search domain, but then calls ``_message_search_ancestor_ids``
-            that will add to the list the ancestors ids. The search is limited
-            to parent messages having an id in ancestor_ids or having
-            parent_id set to False.
-            
-            If ``count==True``, the number of ids is returned instead of the
-            id list. The count is done by hand instead of passing it as an 
-            argument to the search call because we might want to perform
-            a research including parent messages until some ancestor_ids.
-            
-            :param fetch_ancestors: performs an ascended search; will add 
-                                    to fetched msgs all their parents until
-                                    ancestor_ids
-            :param ancestor_ids: used when fetching ancestors
-            :param domain: domain to add to the search; especially child_of
-                           is interesting when dealing with threaded display.
-                           Note that the added domain is anded with the 
-                           default domain.
-            :param limit, offset, count, context: as usual
-        """
-        search_domain = self.message_search_get_domain(cr, uid, ids, context=context)
-        if domain:
-            search_domain += domain
-        message_obj = self.pool.get('mail.message')
-        message_res = message_obj.search(cr, uid, search_domain, limit=limit, offset=offset, count=count, context=context)
-        if not count and fetch_ancestors:
-            message_res += self._message_search_ancestor_ids(cr, uid, ids, message_res, ancestor_ids, context=context) 
-        return message_res
-
-    def message_read(self, cr, uid, ids, fetch_ancestors=False, ancestor_ids=None, 
-                        limit=100, offset=0, domain=None, context=None):
-        """ OpenChatter feature: read the messages related to some threads.
-            This method is used mainly the Chatter widget, to directly have
-            read result instead of searching then reading.
-
-            Please see message_search for more information about the parameters.
-        """
-        message_ids = self.message_search(cr, uid, ids, fetch_ancestors, ancestor_ids,
-            limit, offset, domain, context=context)
-        messages = self.pool.get('mail.message').read(cr, uid, message_ids, context=context)
-
-        """ Retrieve all attachments names """
-        map_id_to_name = dict((attachment_id, '') for message in messages for attachment_id in message['attachment_ids'])
-
-        ids = map_id_to_name.keys()
-        names = self.pool.get('ir.attachment').name_get(cr, uid, ids, context=context)
-        
-        # convert the list of tuples into a dictionnary
-        for name in names: 
-            map_id_to_name[name[0]] = name[1]
-        
-        # give corresponding ids and names to each message
-        for msg in messages:
-            msg["attachments"] = []
-            
-            for attach_id in msg["attachment_ids"]:
-                msg["attachments"].append({'id': attach_id, 'name': map_id_to_name[attach_id]})
-        
-        # Set the threads as read
-        self.message_check_and_set_read(cr, uid, ids, context=context)
-        # Sort and return the messages
-        messages = sorted(messages, key=lambda d: (-d['id']))
-        return messages
-
-    def message_get_pushed_messages(self, cr, uid, ids, fetch_ancestors=False, ancestor_ids=None,
-                            limit=100, offset=0, msg_search_domain=[], context=None):
-        """ OpenChatter: wall: get the pushed notifications and used them
-            to fetch messages to display on the wall.
-            
-            :param fetch_ancestors: performs an ascended search; will add
-                                    to fetched msgs all their parents until
-                                    ancestor_ids
-            :param ancestor_ids: used when fetching ancestors
-            :param domain: domain to add to the search; especially child_of
-                           is interesting when dealing with threaded display
-            :param ascent: performs an ascended search; will add to fetched msgs
-                           all their parents until root_ids
-            :param root_ids: for ascent search
-            :return: list of mail.messages sorted by date
-        """
-        notification_obj = self.pool.get('mail.notification')
-        msg_obj = self.pool.get('mail.message')
-        # update message search
-        for arg in msg_search_domain:
-            if isinstance(arg, (tuple, list)):
-                arg[0] = 'message_id.' + arg[0]
-        # compose final domain
-        domain = [('user_id', '=', uid)] + msg_search_domain
-        # get notifications
-        notification_ids = notification_obj.search(cr, uid, domain, limit=limit, offset=offset, context=context)
-        notifications = notification_obj.browse(cr, uid, notification_ids, context=context)
-        msg_ids = [notification.message_id.id for notification in notifications]
-        # get messages
-        msg_ids = msg_obj.search(cr, uid, [('id', 'in', msg_ids)], context=context)
-        if (fetch_ancestors): msg_ids = self._message_search_ancestor_ids(cr, uid, ids, msg_ids, ancestor_ids, context=context)
-        msgs = msg_obj.read(cr, uid, msg_ids, context=context)
-        return msgs
+    def _message_find_partners(self, cr, uid, message, header_fields=['From'], context=None):
+        """ Find partners related to some header fields of the message. """
+        s = ', '.join([decode(message.get(h)) for h in header_fields if message.get(h)])
+        return [partner_id for email in tools.email_split(s)
+                for partner_id in self.pool.get('res.partner').search(cr, uid, [('email', 'ilike', email)], context=context)]
 
     def _message_find_user_id(self, cr, uid, message, context=None):
-        from_local_part = to_email(decode(message.get('From')))[0]
-        user_ids = self.pool.get('res.users').search(cr, uid, [('login', '=', from_local_part)], context=context)
+        from_local_part = tools.email_split(decode(message.get('From')))[0]
+        # FP Note: canonification required, the minimu: .lower()
+        user_ids = self.pool.get('res.users').search(cr, uid, ['|',
+            ('login', '=', from_local_part),
+            ('email', '=', from_local_part)], context=context)
         return user_ids[0] if user_ids else uid
 
-    #------------------------------------------------------
-    # Mail gateway
-    #------------------------------------------------------
-    # message_process will call either message_new or message_update.
-
     def message_route(self, cr, uid, message, model=None, thread_id=None,
                       custom_values=None, context=None):
         """Attempt to figure out the correct target model, thread_id,
@@ -679,11 +239,11 @@ class mail_thread(osv.Model):
         Multiple values may be returned, if a message had multiple
         recipients matching existing mail.aliases, for example.
 
-        The following heuristics are used, in this order: 
+        The following heuristics are used, in this order:
              1. If the message replies to an existing thread_id, and
                 properly contains the thread model in the 'In-Reply-To'
                 header, use this model/thread_id pair, and ignore
-                custom_value (not needed as no creation will take place) 
+                custom_value (not needed as no creation will take place)
              2. Look for a mail.alias entry matching the message
                 recipient, and use the corresponding model, thread_id,
                 custom_values and user_id.
@@ -719,7 +279,7 @@ class mail_thread(osv.Model):
                 _logger.debug('Routing mail with Message-Id %s: direct reply to model: %s, thread_id: %s, custom_values: %s, uid: %s',
                               message_id, model, thread_id, custom_values, uid)
                 return [(model, thread_id, custom_values, uid)]
-        
+
         # 2. Look for a matching mail.alias entry
         # Delivered-To is a safe bet in most modern MTAs, but we have to fallback on To + Cc values
         # for all the odd MTAs out there, as there is no standard header for the envelope's `rcpt_to` value.
@@ -728,7 +288,7 @@ class mail_thread(osv.Model):
                        decode_header(message, 'Cc'),
                        decode_header(message, 'Resent-To'),
                        decode_header(message, 'Resent-Cc')])
-        local_parts = [e.split('@')[0] for e in to_email(rcpt_tos)]
+        local_parts = [e.split('@')[0] for e in tools.email_split(rcpt_tos)]
         if local_parts:
             mail_alias = self.pool.get('mail.alias')
             alias_ids = mail_alias.search(cr, uid, [('alias_name', 'in', local_parts)])
@@ -742,7 +302,7 @@ class mail_thread(osv.Model):
                                    eval(alias.alias_defaults), user_id))
                 _logger.debug('Routing mail with Message-Id %s: direct alias match: %r', message_id, routes)
                 return routes
-        
+
         # 3. Fallback to the provided parameters, if they work
         model_pool = self.pool.get(model)
         if not thread_id:
@@ -760,19 +320,16 @@ class mail_thread(osv.Model):
                       message_id, model, thread_id, custom_values, uid)
         return [(model, thread_id, custom_values, uid)]
 
-
     def message_process(self, cr, uid, model, message, custom_values=None,
                         save_original=False, strip_attachments=False,
                         thread_id=None, context=None):
         """Process an incoming RFC2822 email message, relying on
            ``mail.message.parse()`` for the parsing operation,
-           and ``message_route()`` to figure out the target model. 
-           
+           and ``message_route()`` to figure out the target model.
+
            Once the target model is known, its ``message_new`` method
            is called with the new message (if the thread record did not exist)
-            or its ``message_update`` method (if it did). Finally,
-           ``message_forward`` is called to automatically notify other
-           people that should receive this message.
+            or its ``message_update`` method (if it did).
 
            :param string model: the fallback model to use if the message
                does not match any of the currently configured mail aliases
@@ -807,11 +364,9 @@ class mail_thread(osv.Model):
         routes = self.message_route(cr, uid, msg_txt, model,
                                     thread_id, custom_values,
                                     context=context)
-        msg = self.pool.get('mail.message').parse_message(msg_txt, save_original=save_original, context=context)
-        msg['state'] = 'received'     
-        if strip_attachments and 'attachments' in msg:
-            del msg['attachments']
-        for model, thread_id, custom_values, user_id in routes:   
+        msg = self.message_parse(cr, uid, msg_txt, save_original=save_original, context=context)
+        if strip_attachments: msg.pop('attachments', None)
+        for model, thread_id, custom_values, user_id in routes:
             if self._name != model:
                 context.update({'thread_model': model})
             model_pool = self.pool.get(model)
@@ -822,10 +377,7 @@ class mail_thread(osv.Model):
                 model_pool.message_update(cr, user_id, [thread_id], msg, context=context)
             else:
                 thread_id = model_pool.message_new(cr, user_id, msg, custom_values, context=context)
-    
-            # Forward the email to other followers
-            self.message_forward(cr, uid, model, [thread_id], msg_txt, context=context)
-            model_pool.message_mark_as_unread(cr, uid, [thread_id], context=context)
+            self.message_post(cr, uid, [thread_id], context=context, **msg)
         return True
 
     def message_new(self, cr, uid, msg_dict, custom_values=None, context=None):
@@ -833,9 +385,7 @@ class mail_thread(osv.Model):
            for a given thread model, if the message did not belong to
            an existing thread.
            The default behavior is to create a new record of the corresponding
-           model (based on some very basic info extracted from the message),
-           then attach the message to the newly created record
-           (by calling ``message_append_dict``).
+           model (based on some very basic info extracted from the message).
            Additional behavior may be implemented by overriding this method.
 
            :param dict msg_dict: a map containing the email details and
@@ -864,14 +414,12 @@ class mail_thread(osv.Model):
         if custom_values and isinstance(custom_values, dict):
             data.update(custom_values)
         res_id = model_pool.create(cr, uid, data, context=context)
-        self.message_append_dict(cr, uid, [res_id], msg_dict, context=context)
         return res_id
 
     def message_update(self, cr, uid, ids, msg_dict, update_vals=None, context=None):
         """Called by ``message_process`` when a new message is received
-           for an existing thread. The default behavior is to create a
-           new mail.message in the given thread (by calling
-           ``message_append_dict``)
+           for an existing thread. The default behavior is to update the record
+           with update_vals taken from the incoming email.
            Additional behavior may be implemented by overriding this
            method.
            :param dict msg_dict: a map containing the email details and
@@ -883,285 +431,248 @@ class mail_thread(osv.Model):
         """
         if update_vals:
             self.write(cr, uid, ids, update_vals, context=context)
-        return self.message_append_dict(cr, uid, ids, msg_dict, context=context)
-
-    def message_thread_followers(self, cr, uid, ids, context=None):
-        """ Returns a list of email addresses of the people following
-            this thread, including the sender of each mail, and the
-            people who were in CC of the messages, if any.
-        """
-        res = {}
-        if isinstance(ids, (str, int, long)):
-            ids = [long(ids)]
-        for thread in self.browse(cr, uid, ids, context=context):
-            l = set()
-            for message in thread.message_ids:
-                l.add((message.user_id and message.user_id.email) or '')
-                l.add(message.email_from or '')
-                l.add(message.email_cc or '')
-            res[thread.id] = filter(None, l)
-        return res
-
-    def message_forward(self, cr, uid, model, thread_ids, msg, email_error=False, context=None):
-        """Sends an email to all people following the given threads.
-           The emails are forwarded immediately, not queued for sending,
-           and not archived.
-
-        :param str model: thread model
-        :param list thread_ids: ids of the thread records
-        :param msg: email.message.Message object to forward
-        :param email_error: optional email address to notify in case
-                            of any delivery error during the forward.
-        :return: True
-        """
-        model_pool = self.pool.get(model)
-        smtp_server_obj = self.pool.get('ir.mail_server')
-        for res in model_pool.browse(cr, uid, thread_ids, context=context):
-            if hasattr(model_pool, 'message_thread_followers'):
-                followers = model_pool.message_thread_followers(cr, uid, [res.id])[res.id]
-            else:
-                followers = self.message_thread_followers(cr, uid, [res.id])[res.id]
-            message_followers_emails = to_email(','.join(filter(None, followers)))
-            message_recipients = to_email(','.join(filter(None,
-                                                                       [decode(msg['from']),
-                                                                        decode(msg['to']),
-                                                                        decode(msg['cc'])])))
-            forward_to = [i for i in message_followers_emails if (i and (i not in message_recipients))]
-            if forward_to:
-                # TODO: we need an interface for this for all types of objects, not just leads
-                if model_pool._columns.get('section_id'):
-                    del msg['reply-to']
-                    msg['reply-to'] = res.section_id.reply_to
-
-                smtp_from, = to_email(msg['from'])
-                msg['from'] = smtp_from
-                msg['to'] =  ", ".join(forward_to)
-                msg['message-id'] = tools.generate_tracking_message_id(res.id)
-                if not smtp_server_obj.send_email(cr, uid, msg) and email_error:
-                    subj = msg['subject']
-                    del msg['subject'], msg['to'], msg['cc'], msg['bcc']
-                    msg['subject'] = _('[OpenERP-Forward-Failed] %s') % subj
-                    msg['to'] = email_error
-                    smtp_server_obj.send_email(cr, uid, msg)
         return True
 
-    def message_partner_by_email(self, cr, uid, email, context=None):
-        """Attempts to return the id of a partner address matching
-           the given ``email``, and the corresponding partner id.
-           Can be used by classes using the ``mail.thread`` mixin
-           to lookup the partner and use it in their implementation
-           of ``message_new`` to link the new record with a
-           corresponding partner.
-           The keys used in the returned dict are meant to map
-           to usual names for relationships towards a partner
-           and one of its addresses.
-
-           :param email: email address for which a partner
-                         should be searched for.
+    def _message_extract_payload(self, message, save_original=False):
+        """Extract body as HTML and attachments from the mail message"""
+        attachments = []
+        body = u''
+        if save_original:
+            attachments.append(('original_email.eml', message.as_string()))
+        if not message.is_multipart() or 'text/' in message.get('content-type', ''):
+            encoding = message.get_content_charset()
+            body = message.get_payload(decode=True)
+            body = tools.ustr(body, encoding, errors='replace')
+            if message.get_content_type() == 'text/plain':
+                # text/plain -> <pre/>
+                body = tools.append_content_to_html(u'', body)
+        else:
+            alternative = (message.get_content_type() == 'multipart/alternative')
+            for part in message.walk():
+                if part.get_content_maintype() == 'multipart':
+                    continue # skip container
+                filename = part.get_filename() # None if normal part
+                encoding = part.get_content_charset() # None if attachment
+                # 1) Explicit Attachments -> attachments
+                if filename or part.get('content-disposition', '').strip().startswith('attachment'):
+                    attachments.append((filename or 'attachment', part.get_payload(decode=True)))
+                    continue
+                # 2) text/plain -> <pre/>
+                if part.get_content_type() == 'text/plain' and (not alternative or not body):
+                    body = tools.append_content_to_html(body, tools.ustr(part.get_payload(decode=True),
+                                                                         encoding, errors='replace'))
+                # 3) text/html -> raw
+                elif part.get_content_type() == 'text/html':
+                    html = tools.ustr(part.get_payload(decode=True), encoding, errors='replace')
+                    if alternative:
+                        body = html
+                    else:
+                        body = tools.append_content_to_html(body, html, plaintext=False)
+                # 4) Anything else -> attachment
+                else:
+                    attachments.append((filename or 'attachment', part.get_payload(decode=True)))
+        return body, attachments
+
+    def message_parse(self, cr, uid, message, save_original=False, context=None):
+        """Parses a string or email.message.Message representing an
+           RFC-2822 email, and returns a generic dict holding the
+           message details.
+
+           :param message: the message to parse
+           :type message: email.message.Message | string | unicode
+           :param bool save_original: whether the returned dict
+               should include an ``original`` attachment containing
+               the source of the message
            :rtype: dict
-           :return: a map of the following form::
-
-                      { 'partner_address_id': id or False,
-                        'partner_id': pid or False }
+           :return: A dict with the following structure, where each
+                    field may not be present if missing in original
+                    message::
+
+                    { 'message-id': msg_id,
+                      'subject': subject,
+                      'from': from,
+                      'to': to,
+                      'cc': cc,
+                      'body': unified_body,
+                      'attachments': [('file1', 'bytes'),
+                                      ('file2', 'bytes')}
+                    }
         """
-        partner_pool = self.pool.get('res.partner')
-        res = {'partner_id': False}
-        if email:
-            email = to_email(email)[0]
-            contact_ids = partner_pool.search(cr, uid, [('email', '=', email)])
-            if contact_ids:
-                contact = partner_pool.browse(cr, uid, contact_ids[0])
-                res['partner_id'] = contact.id
-        return res
-
-    # for backwards-compatibility with old scripts
-    process_email = message_process
+        msg_dict = {}
+        if not isinstance(message, Message):
+            if isinstance(message, unicode):
+                # Warning: message_from_string doesn't always work correctly on unicode,
+                # we must use utf-8 strings here :-(
+                message = message.encode('utf-8')
+            message = email.message_from_string(message)
+
+        message_id = message['message-id']
+        if not message_id:
+            # Very unusual situation, be we should be fault-tolerant here
+            message_id = "<%s@localhost>" % time.time()
+            _logger.debug('Parsing Message without message-id, generating a random one: %s', message_id)
+        msg_dict['message_id'] = message_id
+
+        if 'Subject' in message:
+            msg_dict['subject'] = decode(message.get('Subject'))
+
+        # Envelope fields not stored in  mail.message but made available for message_new()
+        msg_dict['from'] = decode(message.get('from'))
+        msg_dict['to'] = decode(message.get('to'))
+        msg_dict['cc'] = decode(message.get('cc'))
+
+        if 'From' in message:
+            author_ids = self._message_find_partners(cr, uid, message, ['From'], context=context)
+            if author_ids:
+                msg_dict['author_id'] = author_ids[0]
+        partner_ids = self._message_find_partners(cr, uid, message, ['From', 'To', 'Cc'], context=context)
+        msg_dict['partner_ids'] = partner_ids
+
+        if 'Date' in message:
+            date_hdr = decode(message.get('Date'))
+            # convert from email timezone to server timezone
+            date_server_datetime = dateutil.parser.parse(date_hdr).astimezone(pytz.timezone(tools.get_server_timezone()))
+            date_server_datetime_str = date_server_datetime.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
+            msg_dict['date'] = date_server_datetime_str
+
+        if 'In-Reply-To' in message:
+            parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', '=', decode(message['In-Reply-To']))])
+            if parent_ids:
+                msg_dict['parent_id'] = parent_ids[0]
+
+        if 'References' in message and 'parent_id' not in msg_dict:
+            parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', 'in',
+                                                                         [x.strip() for x in decode(message['References']).split()])])
+            if parent_ids:
+                msg_dict['parent_id'] = parent_ids[0]
+
+        msg_dict['body'], msg_dict['attachments'] = self._message_extract_payload(message)
+        return msg_dict
 
     #------------------------------------------------------
     # Note specific
     #------------------------------------------------------
 
     def log(self, cr, uid, id, message, secondary=False, context=None):
-        _logger.warning("log() is deprecated. As this module inherit from \
-                        mail.thread, the message will be managed by this \
-                        module instead of by the res.log mechanism. Please \
-                        use the mail.thread OpenChatter API instead of the \
-                        now deprecated res.log.")
-        self.message_append_note(cr, uid, [id], 'res.log', message, context=context)
+        _logger.warning("log() is deprecated. As this module inherit from "\
+                        "mail.thread, the message will be managed by this "\
+                        "module instead of by the res.log mechanism. Please "\
+                        "use mail_thread.message_post() instead of the "\
+                        "now deprecated res.log.")
+        self.message_post(cr, uid, [id], message, context=context)
+
+    def message_post(self, cr, uid, thread_id, body='', subject=False,
+            type='notification', parent_id=False, attachments=None, context=None, **kwargs):
+        """ Post a new message in an existing thread, returning the new
+            mail.message ID. Extra keyword arguments will be used as default
+            column values for the new mail.message record.
+
+            :param int thread_id: thread ID to post into, or list with one ID
+            :param str body: body of the message, usually raw HTML that will
+                be sanitized
+            :param str subject: optional subject
+            :param str type: mail_message.type
+            :param int parent_id: optional ID of parent message in this thread
+            :param tuple(str,str) attachments: list of attachment tuples in the form
+                ``(name,content)``, where content is NOT base64 encoded
+            :return: ID of newly created mail.message
+        """
+        context = context or {}
+        attachments = attachments or []
+        assert (not thread_id) or isinstance(thread_id, (int,long)) or \
+            (isinstance(thread_id, (list, tuple)) and len(thread_id) == 1), "Invalid thread_id" 
+        if isinstance(thread_id, (list, tuple)):
+            thread_id = thread_id and thread_id[0]
+
+        attachment_ids = []
+        for name, content in attachments:
+            if isinstance(content, unicode):
+                content = content.encode('utf-8')
+            data_attach = {
+                'name': name,
+                'datas': base64.b64encode(str(content)),
+                'datas_fname': name,
+                'description': name,
+                'res_model': context.get('thread_model') or self._name,
+                'res_id': thread_id,
+            }
+            attachment_ids.append((0, 0, data_attach))
 
-    def message_append_note(self, cr, uid, ids, subject=None, body=None, parent_id=False,
-                            type='notification', content_subtype='html', context=None):
-        if content_subtype == 'html':
-            body_html = body
-            body_text = body
-        else:
-            body_html = body
-            body_text = body
-        return self.message_append(cr, uid, ids, subject, body_html, body_text,
-                                    type, parent_id=parent_id,
-                                    content_subtype=content_subtype, context=context)
+        values = kwargs
+        values.update({
+            'model': context.get('thread_model', self._name) if thread_id else False,
+            'res_id': thread_id or False,
+            'body': body,
+            'subject': subject,
+            'type': type,
+            'parent_id': parent_id,
+            'attachment_ids': attachment_ids,
+        })
+        for x in ('from', 'to', 'cc'): values.pop(x, None) # Avoid warnings 
+        return self.pool.get('mail.message').create(cr, uid, values, context=context)
 
     #------------------------------------------------------
-    # Subscription mechanism
+    # Followers API
     #------------------------------------------------------
 
-    def message_get_monitored_follower_fields(self, cr, uid, ids, context=None):
-        """ Returns a list of fields containing a res.user.id. Those fields
-            will be checked to automatically subscribe those users.
+    def message_subscribe_users(self, cr, uid, ids, user_ids=None, context=None):
+        """ Wrapper on message_subscribe, using users. If user_ids is not
+            provided, subscribe uid instead. """
+        if not user_ids: user_ids = [uid]
+        partner_ids = [user.partner_id.id for user in self.pool.get('res.users').browse(cr, uid, user_ids, context=context)]
+        return self.message_subscribe(cr, uid, ids, partner_ids, context=context)
+
+    def message_subscribe(self, cr, uid, ids, partner_ids, context=None):
+        """ Add partners to the records followers.
+            :param partner_ids: a list of partner_ids to subscribe
+            :param return: new value of followers if read_back key in context
         """
+        self.write(cr, uid, ids, {'message_follower_ids': [(4, pid) for pid in partner_ids]}, context=context)
+        if context and context.get('read_back'):
+            return [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_follower_ids]
         return []
 
-    def message_subscribe(self, cr, uid, ids, user_ids = None, context=None):
-        """ Subscribe the user (or user_ids) to the current document.
-            
-            :param user_ids: a list of user_ids; if not set, subscribe
-                             uid instead
-            :param return: new value of followers, for Chatter
-        """
-        to_subscribe_uids = [uid] if user_ids is None else user_ids
-        write_res = self.write(cr, uid, ids, {'message_follower_ids': self.message_subscribe_get_command(cr, uid, to_subscribe_uids, context)}, context=context)
-        return [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_follower_ids]
-
-    def message_subscribe_get_command(self, cr, uid, follower_ids, context=None):
-        """ Generate the many2many command to add followers. """
-        return [(4, id) for id in follower_ids]
-
-    def message_unsubscribe(self, cr, uid, ids, user_ids = None, context=None):
-        """ Unsubscribe the user (or user_ids) from the current document.
-            
-            :param user_ids: a list of user_ids; if not set, subscribe
-                             uid instead
-            :param return: new value of followers, for Chatter
-        """
-        to_unsubscribe_uids = [uid] if user_ids is None else user_ids
-        write_res = self.write(cr, uid, ids, {'message_follower_ids': self.message_unsubscribe_get_command(cr, uid, to_unsubscribe_uids, context)}, context=context)
-        return [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_follower_ids]
-
-    def message_unsubscribe_get_command(self, cr, uid, follower_ids, context=None):
-        """ Generate the many2many command to remove followers. """
-        return [(3, id) for id in follower_ids]
-
-    #------------------------------------------------------
-    # Notification API
-    #------------------------------------------------------
-
-    def message_create_notify_by_email(self, cr, uid, new_msg_values, user_to_notify_ids, context=None):
-        """ When creating a new message and pushing notifications, emails
-            must be send if users have chosen to receive notifications
-            by email via the notification_email_pref field.
-            
-            ``notification_email_pref`` can have 3 values :
-            - all: receive all notification by email (for example for shared
-              users)
-            - to_me: messages send directly to me (@login, messages on res.users)
-            - never: never receive notifications
-            Note that an user should never receive notifications for messages
-            he has created.
-            
-            :param new_msg_values: dictionary of message values, those that
-                                   are given to the create method
-            :param user_to_notify_ids: list of user_ids, user that will
-                                       receive a notification on their Wall
+    def message_unsubscribe_users(self, cr, uid, ids, user_ids=None, context=None):
+        """ Wrapper on message_subscribe, using users. If user_ids is not
+            provided, unsubscribe uid instead. """
+        if not user_ids: user_ids = [uid]
+        partner_ids = [user.partner_id.id for user in self.pool.get('res.users').browse(cr, uid, user_ids, context=context)]
+        return self.message_unsubscribe(cr, uid, ids, partner_ids, context=context)
+
+    def message_unsubscribe(self, cr, uid, ids, partner_ids, context=None):
+        """ Remove partners from the records followers.
+            :param partner_ids: a list of partner_ids to unsubscribe
+            :param return: new value of followers if read_back key in context
         """
-        message_obj = self.pool.get('mail.message')
-        res_users_obj = self.pool.get('res.users')
-        body = new_msg_values.get('body_html', '') if new_msg_values.get('content_subtype') == 'html' else new_msg_values.get('body_text', '')
-        
-        # remove message writer
-        if user_to_notify_ids.count(new_msg_values.get('user_id')) > 0:
-            user_to_notify_ids.remove(new_msg_values.get('user_id'))
-
-        # try to find an email_to
-        email_to = ''
-        for user in res_users_obj.browse(cr, uid, user_to_notify_ids, context=context):
-            # TO BE REFACTORED BY FP, JUSTE REMOVED TO_ME, NOT SURE WHAT S NEW BEHAVIOR
-            if not user.notification_email_pref == 'all':
-                continue
-            if not user.email:
-                continue
-            email_to = '%s, %s' % (email_to, user.email)
-            email_to = email_to.lstrip(', ')
-        
-        # did not find any email address: not necessary to create an email
-        if not email_to:
-            return
-        
-        # try to find an email_from
-        current_user = res_users_obj.browse(cr, uid, [uid], context=context)[0]
-        email_from = new_msg_values.get('email_from')
-        if not email_from:
-            email_from = current_user.email
-        
-        # get email content, create it (with mail_message.create)
-        email_values = self.message_create_notify_get_email_dict(cr, uid, new_msg_values, email_from, email_to, context)
-        email_id = message_obj.create(cr, uid, email_values, context=context)
-        return email_id
-    
-    def message_create_notify_get_email_dict(self, cr, uid, new_msg_values, email_from, email_to, context=None):
-        values = dict(new_msg_values)
-        
-        body_html = new_msg_values.get('body_html', '')
-        if body_html:
-            body_html += '\n\n----------\nThis email was send automatically by OpenERP, because you have subscribed to a document.'
-        body_text = new_msg_values.get('body_text', '')
-        if body_text:
-            body_text += '\n\n----------\nThis email was send automatically by OpenERP, because you have subscribed to a document.'
-        values.update({
-            'type': 'email',
-            'state': 'outgoing',
-            'email_from': email_from,
-            'email_to': email_to,
-            'subject': 'New message',
-            'content_subtype': new_msg_values.get('content_subtype', 'plain'),
-            'body_html': body_html,
-            'body_text': body_text,
-            'auto_delete': True,
-            'res_model': '',
-            'res_id': False,
-        })
-        return values
-
-    def message_remove_pushed_notifications(self, cr, uid, ids, msg_ids, remove_childs=True, context=None):
-        notif_obj = self.pool.get('mail.notification')
-        msg_obj = self.pool.get('mail.message')
-        if remove_childs:
-            notif_msg_ids = msg_obj.search(cr, uid, [('id', 'child_of', msg_ids)], context=context)
-        else:
-            notif_msg_ids = msg_ids
-        to_del_notif_ids = notif_obj.search(cr, uid, ['&', ('user_id', '=', uid), ('message_id', 'in', notif_msg_ids)], context=context)
-        return notif_obj.unlink(cr, uid, to_del_notif_ids, context=context)
+        self.write(cr, uid, ids, {'message_follower_ids': [(3, pid) for pid in partner_ids]}, context=context)
+        if context and context.get('read_back'):
+            return [follower.id for thread in self.browse(cr, uid, ids, context=context) for follower in thread.message_follower_ids]
+        return []
 
     #------------------------------------------------------
-    # Thread_state
+    # Thread state
     #------------------------------------------------------
 
-    def message_create_set_unread(self, cr, uid, ids, context=None):
-        """ When creating a new message, set as unread if uid is not the
-            object responsible. """
-        for obj in self.browse(cr, uid, ids, context=context):
-            if obj.message_state and ('user_id' in obj._columns.keys()) and (not obj.user_id or obj.user_id.id != uid) :
-                self.message_mark_as_unread(cr, uid, [obj.id], context=context)
-
-    def message_check_and_set_unread(self, cr, uid, ids, context=None):
-        """ Set unread if uid is the object responsible or if the object has
-            no responsible. """
-        for obj in self.browse(cr, uid, ids, context=context):
-            if obj.message_state and self._columns.get('user_id') and (not obj.user_id or obj.user_id.id == uid):
-                self.message_mark_as_unread(cr, uid, [obj.id], context=context)
-
     def message_mark_as_unread(self, cr, uid, ids, context=None):
         """ Set as unread. """
-        return self.write(cr, uid, ids, {'message_state': False}, context=context)
+        partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
+        cr.execute('''
+            UPDATE mail_notification SET
+                read=false
+            WHERE
+                message_id IN (SELECT id from mail_message where res_id=any(%s) and model=%s limit 1) and
+                partner_id = %s
+        ''', (ids, self._name, partner_id))
+        return True
 
-    def message_check_and_set_read(self, cr, uid, ids, context=None):
-        """ Set read if uid is the object responsible. """
-        for obj in self.browse(cr, uid, ids, context=context):
-            if not obj.message_state and self._columns.get('user_id') and obj.user_id and obj.user_id.id == uid:
-                self.message_mark_as_read(cr, uid, [obj.id], context=context)
-    
     def message_mark_as_read(self, cr, uid, ids, context=None):
         """ Set as read. """
-        return self.write(cr, uid, ids, {'message_state': True}, context=context)
-
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+        partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
+        cr.execute('''
+            UPDATE mail_notification SET
+                read=true
+            WHERE
+                message_id IN (SELECT id FROM mail_message WHERE res_id=ANY(%s) AND model=%s) AND
+                partner_id = %s
+        ''', (ids, self._name, partner_id))
+        return True
index d3a61b3..2988cee 100644 (file)
 #
 ##############################################################################
 
-from osv import osv
+from osv import osv, fields
 
 class res_partner_mail(osv.Model):
-    """ Inherits partner and adds CRM information in the partner form """
+    """ Update partner to add a field about notification preferences """
     _name = "res.partner"
     _inherit = ['res.partner', 'mail.thread']
 
-    def message_search_get_domain(self, cr, uid, ids, context=None):
-        """ Override of message_search_get_domain for partner discussion page.
-            The purpose is to add messages directly sent to the partner. It also
-            adds messages pushed to the related user, if any, using @login.
-        """
-        initial_domain = super(res_partner_mail, self).message_search_get_domain(cr, uid, ids, context=context)
-        # to avoid models inheriting from res.partner
-        if self._name != 'res.partner':
-            return initial_domain
-        # add message linked to the partner
-        search_domain = ['|'] + initial_domain + ['|', ('partner_id', 'in', ids), ('partner_ids', 'in', ids)]
-        # if partner is linked to a user: find @login
-        res_users_obj = self.pool.get('res.users')
-        user_ids = res_users_obj.search(cr, uid, [('partner_id', 'in', ids)], context=context)
-        for user in res_users_obj.browse(cr, uid, user_ids, context=context):
-            search_domain = ['|'] + search_domain + ['|', ('body_text', 'like', '@%s' % (user.login)), ('body_html', 'like', '@%s' % (user.login))]
-        return search_domain
+    _columns = {
+        'notification_email_send': fields.selection([
+            ('all', 'All feeds'),
+            ('comment', 'Comments and Emails'),
+            ('email', 'Emails only'),
+            ('none', 'Never')
+            ], 'Receive Feeds by Email', required=True,
+            help="Choose in which case you want to receive an email when you "\
+                  "receive new feeds."),
+    }
+
+    _defaults = {
+        'notification_email_send': lambda *args: 'comment'
+    }
+
 
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
index 63280bb..1bc1930 100644 (file)
@@ -34,32 +34,24 @@ class res_users(osv.Model):
     _inherits = {'mail.alias': 'alias_id'}
 
     _columns = {
-        'notification_email_pref': fields.selection([
-            ('all', 'All Feeds'),
-            ('to_me', 'Only send directly to me'),
-            ('none', 'Never')
-            ], 'Receive Feeds by Email', required=True,
-            help="Choose in which case you want to receive an email when you "\
-                 "receive new feeds."),
         'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="cascade", required=True, 
             help="Email address internally associated with this user. Incoming "\
                  "emails will appear in the user's notifications."),
     }
     
     _defaults = {
-        'notification_email_pref': 'to_me',
         'alias_domain': False, # always hide alias during creation
     }
 
     def __init__(self, pool, cr):
-        """ Override of __init__ to add access rights on notification_email_pref
+        """ Override of __init__ to add access rights on notification_email_send
             field. Access rights are disabled by default, but allowed on
             fields defined in self.SELF_WRITEABLE_FIELDS.
         """
         init_res = super(res_users, self).__init__(pool, cr)
         # duplicate list to avoid modifying the original reference
         self.SELF_WRITEABLE_FIELDS = list(self.SELF_WRITEABLE_FIELDS)
-        self.SELF_WRITEABLE_FIELDS.append('notification_email_pref')
+        self.SELF_WRITEABLE_FIELDS.append('notification_email_send')
         return init_res
 
     def _auto_init(self, cr, context=None):
@@ -73,23 +65,23 @@ class res_users(osv.Model):
         alias_id = mail_alias.create_unique_alias(cr, uid, {'alias_name': data['login']}, model_name=self._name, context=context)
         data['alias_id'] = alias_id
         data.pop('alias_name', None) # prevent errors during copy()
+
         # create user that follows its related partner
         user_id = super(res_users, self).create(cr, uid, data, context=context)
         user = self.browse(cr, uid, user_id, context=context)
-        self.pool.get('res.partner').message_subscribe(cr, uid, [user.partner_id.id], [user_id], context=context)
+        self.pool.get('res.partner').message_subscribe(cr, uid, [user.partner_id.id], [user.partner_id.id], context=context)
         # alias
         mail_alias.write(cr, SUPERUSER_ID, [alias_id], {"alias_force_thread_id": user_id}, context)
         # create a welcome message
-        self.create_welcome_message(cr, uid, user, context=context)
+        self._create_welcome_message(cr, uid, user, context=context)
         return user_id
 
-    def create_welcome_message(self, cr, uid, user, context=None):
+    def _create_welcome_message(self, cr, uid, user, context=None):
         company_name = user.company_id.name if user.company_id else _('the company')
-        subject = '''%s has joined %s.''' % (user.name, company_name)
-        body = '''Welcome to OpenERP !''' 
-        # TODO change 1 into user.id but catch errors
-        return self.pool.get('res.partner').message_append_note(cr, SUPERUSER_ID, [user.partner_id.id],
-            subject=subject, body=body, type='comment', content_subtype='html', context=context)
+        body = _('%s has joined %s.') % (user.name, company_name)
+        # TODO change SUPERUSER_ID into user.id but catch errors
+        return self.pool.get('res.partner').message_post(cr, SUPERUSER_ID, [user.partner_id.id],
+            body=body, context=context)
 
     def write(self, cr, uid, ids, vals, context=None):
         # User alias is sync'ed with login
@@ -104,52 +96,19 @@ class res_users(osv.Model):
         alias_pool.unlink(cr, uid, alias_ids, context=context)
         return res
 
-    # --------------------------------------------------
-    # Wrappers on partner methods for Chatter
-    # #FIXME: another branch holds a refactoring of mail.thread
-    # that should help cleaning those wrappers
-    # --------------------------------------------------
-
-    def message_append(self, cr, uid, threads, subject, body_text=None, body_html=None,
-                        type='email', email_date=None, parent_id=False,
-                        content_subtype='plain', state=None,
-                        partner_ids=None, email_from=False, email_to=False,
-                        email_cc=None, email_bcc=None, reply_to=None,
-                        headers=None, message_id=False, references=None,
-                        attachments=None, original=None, context=None):
-        for user in self.browse(cr, uid, threads, context=context):
-            user.partner_id.message_append(subject, body_text, body_html, type, email_date, parent_id,
-                content_subtype, state, partner_ids, email_from, email_to, email_cc, email_bcc, reply_to,
-                headers, message_id, references, attachments, original)
-
-    def message_read(self, cr, uid, ids, fetch_ancestors=False, ancestor_ids=None, 
-                        limit=100, offset=0, domain=None, context=None):
-        for user in self.browse(cr, uid, ids, context=context):
-            return user.partner_id.message_read(fetch_ancestors, ancestor_ids, limit, offset, domain)
-
-    def message_search(self, cr, uid, ids, fetch_ancestors=False, ancestor_ids=None, 
-                        limit=100, offset=0, domain=None, count=False, context=None):
-        for user in self.browse(cr, uid, ids, context=context):
-            return user.partner_id.message_search(fetch_ancestors, ancestor_ids, limit, offset, domain, count)
-
-    def message_subscribe(self, cr, uid, ids, user_ids = None, context=None):
-        for user in self.browse(cr, uid, ids, context=context):
-            return user.partner_id.message_subscribe(user_ids)
-
-    def message_unsubscribe(self, cr, uid, ids, user_ids = None, context=None):
-        for user in self.browse(cr, uid, ids, context=context):
-            return user.partner_id.message_unsubscribe(user_ids)
-
+    def message_post(self, cr, uid, thread_id, **kwargs):
+        partner_id = self.pool.get('res.users').browse(cr, uid, thread_id)[0].partner_id.id
+        return self.pool.get('res.partner').message_post(cr, uid, partner_id, **kwargs)
 
 class res_users_mail_group(osv.Model):
-    """ Update of res.groups class
-        - if adding/removing users from a group, check mail.groups linked to
-          this user group, and subscribe / unsubscribe them from the discussion
-          group. This is done by overriding the write method.
+    """ Update of res.users class
+        - if adding groups to an user, check mail.groups linked to this user
+          group, and the user. This is done by overriding the write method.
     """
     _name = 'res.users'
     _inherit = ['res.users']
 
+    # FP Note: to improve, post processing may be better ?
     def write(self, cr, uid, ids, vals, context=None):
         write_res = super(res_users_mail_group, self).write(cr, uid, ids, vals, context=context)
         if vals.get('groups_id'):
@@ -158,26 +117,25 @@ class res_users_mail_group(osv.Model):
             user_group_ids += [id for command in vals['groups_id'] if command[0] == 6 for id in command[2]]
             mail_group_obj = self.pool.get('mail.group')
             mail_group_ids = mail_group_obj.search(cr, uid, [('group_ids', 'in', user_group_ids)], context=context)
-            mail_group_obj.message_subscribe(cr, uid, mail_group_ids, ids, context=context)
+            mail_group_obj.message_subscribe_users(cr, uid, mail_group_ids, ids, context=context)
         return write_res
 
 class res_groups_mail_group(osv.Model):
     """ Update of res.groups class
-        - if adding/removing users from a group, check mail.groups linked to
-          this user group, and subscribe / unsubscribe them from the discussion
-          group. This is done by overriding the write method.
+        - if adding users from a group, check mail.groups linked to this user
+          group and subscribe them. This is done by overriding the write method.
     """
     _name = 'res.groups'
     _inherit = 'res.groups'
 
+    # FP Note: to improve, post processeing, after the super may be better
     def write(self, cr, uid, ids, vals, context=None):
+        write_res = super(res_groups_mail_group, self).write(cr, uid, ids, vals, context=context)
         if vals.get('users'):
             # form: {'group_ids': [(3, 10), (3, 3), (4, 10), (4, 3)]} or {'group_ids': [(6, 0, [ids]}
             user_ids = [command[1] for command in vals['users'] if command[0] == 4]
             user_ids += [id for command in vals['users'] if command[0] == 6 for id in command[2]]
             mail_group_obj = self.pool.get('mail.group')
             mail_group_ids = mail_group_obj.search(cr, uid, [('group_ids', 'in', ids)], context=context)
-            mail_group_obj.message_subscribe(cr, uid, mail_group_ids, user_ids, context=context)
-        return super(res_groups_mail_group, self).write(cr, uid, ids, vals, context=context)
-
-# vim:et:
+            mail_group_obj.message_subscribe_users(cr, uid, mail_group_ids, user_ids, context=context)
+        return write_res
index 005d71c..a119765 100644 (file)
@@ -10,7 +10,7 @@
             <field name="arch" type="xml">
             <data>
                 <field name="email" position="before">
-                    <field name="notification_email_pref" readonly="0"/>
+                    <field name="notification_email_send" readonly="0"/>
                 </field>
             </data>
             </field>
@@ -24,7 +24,7 @@
             <field name="arch" type="xml">
             <data>
                 <field name="email" position="before">
-                    <field name="notification_email_pref"/>
+                    <field name="notification_email_send"/>
                 </field>
                 <field name="email" position="after">
                     <field name="alias_domain" invisible="1"/>
index 28a32b8..240627c 100644 (file)
@@ -1,56 +1,46 @@
 /* ------------------------------------------------------------ */
-/* Wall
+/* Reset because of ugly display of end of August
 /* ------------------------------------------------------------ */
 
-.openerp div.oe_mail_wall {
-    overflow: auto;
+.openerp .oe_mail_wall ul, .openerp .oe_mail_wall li {
+    list-style-type: none;
     padding: 0;
-    background: white;
-}
-
-.openerp div.oe_mail_wall_main {
-    float: left;
-    width: 560px;
-    margin: 8px;
+    margin: 0;
 }
 
-.openerp div.oe_mail_wall_aside {
-    margin-left: 565px;
-    margin: 8px;
+.openerp .oe_chatter ul, .openerp .oe_chatter li {
+    list-style-type: none;
+    padding: 0;
+    margin: 0;
 }
 
-.openerp div.oe_mail_wall_action {
-    padding: 8px;
-    background: #eee;
-    border: 1px solid #ccc;
-}
 
-.openerp div.oe_mail_wall_action:after {
-    content: "";
-    display: block;
-    clear: both; 
-}
+/* ------------------------------------------------------------ */
+/* Wall
+/* ------------------------------------------------------------ */
 
-.openerp div.oe_mail_wall_action .oe_mail_msg_content {
-    width: 484px;
+.openerp div.oe_mail_wall {
+    overflow: auto;
+    padding: 0;
+    background: white;
 }
 
-.openerp div.oe_mail_wall_action textarea.oe_mail_compose_textarea,
-.openerp div.oe_mail_wall_action div.oe_mail_compose_message_body_text textarea {
-    width: 474px;
-    height: 60px;
-    padding: 4px;
-    margin-bottom: 2px;
+.openerp div.oe_mail_wall div.oe_mail_wall_aside {
+    margin-left: 565px;
+    margin: 8px;
 }
 
-.openerp ul.oe_mail_wall_threads {
-    margin-top: 8px;
+.openerp div.oe_mail_wall ul.oe_mail_wall_threads {
+    float: left;
+    width: 560px;
+    margin: 8px;
+    list-style-type: none;
 }
 
 /* Specific display of threads in the wall */
 /* ------------------------------------------------------------ */
 
-.openerp ul.oe_mail_wall_threads .oe_mail_msg_content textarea {
+.openerp ul.oe_mail_wall_threads .oe_mail_msg_content textarea.oe_mail_compose_textarea {
     width: 434px;
     height: 30px;
     padding: 4px;
     width: 440px;
 }
 
-.openerp div.oe_mail_wall_more {
-    text-align: center;
-    display: none;
-}
-
 
 /* ------------------------------------------------------------ */
 /* RecordThread
     overflow: auto;
 }
 
-.openerp div.oe_mail_recthread {
-    /*overflow: auto;*/
-}
-
 .openerp div.oe_mail_recthread_main {
     float: left;
     width: 560px;
     clear: both; 
 }
 
-/* default textarea (oe_mail_compose_textarea), and body_text textarea for compose form view */
+/* default textarea (oe_mail_compose_textarea), and body textarea for compose form view */
 .openerp .oe_mail_msg_content textarea.oe_mail_compose_textarea,
-.openerp .oe_mail_msg_content div.oe_mail_compose_message_body_text textarea {
+.openerp .oe_mail_msg_content div.oe_mail_compose_message_body textarea {
     width: 474px;
     height: 60px;
     padding: 4px;
     border: 1px solid #cccccc;
 }
 
-/* default textarea (oe_mail_compose_textarea), and body_text textarea for compose form view */
+/* default textarea (oe_mail_compose_textarea), and body textarea for compose form view */
 .openerp .oe_mail_msg_content textarea.oe_mail_compose_textarea:focus,
-.openerp .oe_mail_msg_content div.oe_mail_compose_message_body_text textarea:focus {
+.openerp .oe_mail_msg_content div.oe_mail_compose_message_body textarea:focus {
     outline: 0;
     border-color: rgba(82, 168, 236, 0.8);
     -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
index 4cd1339..ba7ed36 100644 (file)
@@ -2,10 +2,6 @@
 /* Compose Message Wizard Form */
 /* ------------------------------ */
 
-.openerp tr td .oe_form_field.oe_mail_compose_message_invisible {
-    display: none;
-}
-
 .openerp .oe_mail_compose_message_icons {
     text-align: right;
 }
index e3d688e..a5a64a5 100644 (file)
@@ -17,8 +17,15 @@ openerp.mail = function(session) {
      */
 
     session.web.FormView = session.web.FormView.extend({
+        // TDE FIXME TODO: CHECK WITH NEW BRANCH
         do_action: function(action, on_close) {
-            if (action.res_model == 'mail.compose.message' && this.fields && this.fields.message_ids && this.fields.message_ids.view.get("actual_mode") != 'create') {
+            if (action.res_model == 'mail.compose.message' &&
+                this.fields && this.fields.message_ids &&
+                this.fields.message_ids.view.get("actual_mode") != 'create') {
+                // debug
+                console.groupCollapsed('FormView do_action on mail.compose.message');
+                console.log('message_ids field:', this.fields.message_ids);
+                console.groupEnd();
                 var record_thread = this.fields.message_ids;
                 var thread = record_thread.thread;
                 thread.instantiate_composition_form('comment', true, false, 0, action.context);
@@ -30,7 +37,6 @@ openerp.mail = function(session) {
         },
     });
 
-
     /**
      * ------------------------------------------------------------
      * ChatterUtils
@@ -38,181 +44,37 @@ openerp.mail = function(session) {
      * 
      * This class holds a few tools method that will be used by
      * the various Chatter widgets.
+     *
+     * Some regular expressions not used anymore, kept because I want to
+     * - (^|\s)@((\w|@|\.)*): @login@log.log
+     *      1. '(void)'
+     *      2. login@log.log
+     * - (^|\s)\[(\w+).(\w+),(\d)\|*((\w|[@ .,])*)\]: [ir.attachment,3|My Label],
+     *   for internal links
+     *      1. '(void)'
+     *      2. 'ir'
+     *      3. 'attachment'
+     *      4. '3'
+     *      5. 'My Label'
      */
 
     mail.ChatterUtils = {
 
-        /**
-        * mail_int_mapping: structure to keep a trace of internal links mapping
-        *      mail_int_mapping['model'] = {
-        *          'name_get': [[id,label], [id,label], ...]
-        *          'fetch_ids': [id, id, ...] } */
-        //var mail_int_mapping = {};
-        
-        /**
-        * mail_msg_struct: structure to orrganize chatter messages
-        */
-        //var mail_msg_struct = {}; // TODO: USE IT OR NOT :)
-
-        /* generic chatter events binding */
-        bind_events: function(widget) {
-            // event: click on an internal link to a document: model, login
-            widget.$el.delegate('a.oe_mail_internal_link', 'click', function (event) {
-                event.preventDefault();
-                // lazy implementation: fetch data and try to redirect
-                if (! event.srcElement.dataset.resModel) return false;
-                else var res_model = event.srcElement.dataset.resModel;
-                var res_login = event.srcElement.dataset.resLogin;
-                if (! res_login) return false;
-                var ds = new session.web.DataSet(widget, res_model);
-                var defer = ds.call('search', [[['login', '=', res_login]]]).pipe(function (records) {
-                    if (records[0]) {
-                        widget.do_action({ type: 'ir.actions.act_window', res_model: res_model, res_id: parseInt(records[0]), views: [[false, 'form']]});
-                    }
-                    else return false;
-                });
-            });
-        },
-
         /** get an image in /web/binary/image?... */
         get_image: function(session_prefix, session_id, model, field, id) {
             return session_prefix + '/web/binary/image?session_id=' + session_id + '&model=' + model + '&field=' + field + '&id=' + (id || '');
         },
 
-        /** checks if tue current user is the message author */
+        /** check if the current user is the message author */
         is_author: function (widget, message_user_id) {
             return (widget.session && widget.session.uid != 0 && widget.session.uid == message_user_id);
         },
 
-        /**
-        * Add records to comments_structure array
-        * @param {Array} records records from mail.message sorted by date desc
-        * @returns {Object} cs comments_structure: dict
-        *       cs.model_to_root_ids = {model: [root_ids], }
-        *       cs.new_root_ids = [new_root_ids]
-        *       cs.root_ids = [root_ids]
-        *       cs.msgs = {record.id: record,}
-        *       cs.tree_struct = {record.id: {
-        *           'level': record_level in hierarchy, 0 is root,
-        *           'msg_nbr': number of childs,
-        *           'direct_childs': [msg_ids],
-        *           'all_childs': [msg_ids],
-        *           'for_thread_msgs': [records],
-        *           'ancestors': [msg_ids], } }
-        */
-        records_struct_add_records: function(cs, records, parent_id) {
-            var cur_iter = 0; var max_iter = 10; var modif = true;
-            while ( modif && (cur_iter++) < max_iter) {
-                modif = false;
-                _(records).each(function (record) {
-                    // root and not yet recorded
-                    if ( (record.parent_id == false || record.parent_id[0] == parent_id) && ! cs['msgs'][record.id]) {
-                        // add to model -> root_list ids
-                        if (! cs['model_to_root_ids'][record.model]) cs['model_to_root_ids'][record.model] = [record.id];
-                        else cs['model_to_root_ids'][record.model].push(record.id);
-                        // add root data
-                        cs['new_root_ids'].push(record.id);
-                        // add record
-                        cs['tree_struct'][record.id] = {'level': 0, 'direct_childs': [], 'all_childs': [], 'for_thread_msgs': [record], 'msg_nbr': -1, 'ancestors': []};
-                        cs['msgs'][record.id] = record;
-                        modif = true;
-                    }
-                    // not yet recorded, but parent is recorded
-                    else if (! cs['msgs'][record.id] && cs['msgs'][record.parent_id[0]]) {
-                        var parent_level = cs['tree_struct'][record.parent_id[0]]['level'];
-                        // update parent structure
-                        cs['tree_struct'][record.parent_id[0]]['direct_childs'].push(record.id);
-                        cs['tree_struct'][record.parent_id[0]]['for_thread_msgs'].push(record);
-                        // update ancestors structure
-                        for (ancestor_id in cs['tree_struct'][record.parent_id[0]]['ancestors']) {
-                            cs['tree_struct'][ancestor_id]['all_childs'].push(record.id);
-                        }
-                        // add record
-                        cs['tree_struct'][record.id] = {'level': parent_level+1, 'direct_childs': [], 'all_childs': [], 'for_thread_msgs': [], 'msg_nbr': -1, 'ancestors': []};
-                        cs['msgs'][record.id] = record;
-                        modif = true;
-                    }
-                });
-            }
-            return cs;
-        },
-
-        /* copy cs.new_root_ids into cs.root_ids */
-        records_struct_update_after_display: function(cs) {
-            // update TODO
-            cs['root_ids'] = _.union(cs['root_ids'], cs['new_root_ids']);
-            cs['new_root_ids'] = [];
-            return cs;
-        },
-
-        /**
-         *    CONTENT MANIPULATION
-         * 
-         * Regular expressions
-         * - (^|\s)@((\w|@|\.)*): @login@log.log, supports inner '@' for
-         *   logins that are emails
-         *      1. '(void)'
-         *      2. login@log.log
-         * - (^|\s)\[(\w+).(\w+),(\d)\|*((\w|[@ .,])*)\]: [ir.attachment,3|My Label],
-         *   for internal links to model ir.attachment, id=3, and with
-         *   optional label 'My Label'. Note that having a '|Label' is not
-         *   mandatory, because the regex should still be correct.
-         *      1. '(void)'
-         *      2. 'ir'
-         *      3. 'attachment'
-         *      4. '3'
-         *      5. 'My Label'
-         */
-
-        /** Removes html tags, except b, em, br, ul, li */
-        do_text_remove_html_tags: function (string) {
-            var html = $('<div/>').text(string.replace(/\s+/g, ' ')).html().replace(new RegExp('&lt;(/)?(b|em|br|br /|ul|li|div)\\s*&gt;', 'gi'), '<$1$2>');
-            return html;
-        },
-        
-        /** Replaces line breaks by html line breaks (br) */
-        do_text_nl2br: function (str, is_xhtml) {   
-            var break_tag = (is_xhtml || typeof is_xhtml === 'undefined') ? '<br />' : '<br>';    
-            return (str + '').replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1'+ break_tag +'$2');
-        },
-
-        /* Add a prefix before each new line of the original string */
-        do_text_quote: function (str, prefix) {
-            return str.replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1'+ break_tag +'$2' + prefix || '> ');
-        },
-
-        /**
-         * Replaces some expressions
-         * - @login - shorcut to link to a res.user, given its login
-         * - [ir.attachment,3|My Label] - shortcut to an internal
-         *   document
+        /** Replaces some expressions
          * - :name - shortcut to an image
          */
         do_replace_expressions: function (string) {
-            var self = this;
             var icon_list = ['al', 'pinky']
-            /* shortcut to user: @login */
-            var regex_login = new RegExp(/(^|\s)@((\w|@|\.)*)/g);
-            var regex_res = regex_login.exec(string);
-            while (regex_res != null) {
-                var login = regex_res[2];
-                string = string.replace(regex_res[0], regex_res[1] + '<a href="#" class="oe_mail_internal_link" data-res-model="res.users" data-res-login = ' + login + '>@' + login + '</a>');
-                regex_res = regex_login.exec(string);
-            }
-            /* shortcut for internal document */
-            var regex_login = new RegExp(/(^|\s)\[(\w+).(\w+),(\d)\|*((\w|[@ .,])*)\]/g);
-            var regex_res = regex_login.exec(string);
-            while (regex_res != null) {
-                var res_model = regex_res[2] + '.' + regex_res[3];
-                var res_id = regex_res[4];
-                if (! regex_res[5]) {
-                    var label = res_model + ':' + res_id }
-                else {
-                    var label = regex_res[5];
-                }
-                string = string.replace(regex_res[0], regex_res[1] + '<a href="#model=' + res_model + '&id=' + res_id + '>' + label + '</a>');
-                regex_res = regex_login.exec(string);
-            }
             /* special shortcut: :name, try to find an icon if in list */
             var regex_login = new RegExp(/(^|\s):((\w)*)/g);
             var regex_res = regex_login.exec(string);
@@ -224,46 +86,6 @@ openerp.mail = function(session) {
             }
             return string;
         },
-
-        /**
-         * Checks a string to find an expression that will be replaced
-         * by an internal link and requiring a name_get to replace
-         * the expression.
-         * :param mapping: structure to keep a trace of internal links mapping
-         *                  mapping['model'] = {
-         *                      name_get': [[id,label], [id,label], ...]
-         *                      'to_fetch_ids': [id, id, ...]
-         *                  }
-         * CURRENTLY NOT IMPLEMENTED */
-        do_check_for_name_get_mapping: function(string, mapping) {
-            /* shortcut to user: @login */
-            //var regex_login = new RegExp(/(^|\s)@((\w|@|\.)*)/g);
-            //var regex_res = regex_login.exec(string);
-            //while (regex_res != null) {
-                //var login = regex_res[2];
-                //if (! ('res.users' in this.map_hash)) { this.map_hash['res.users']['name'] = []; }
-                //this.map_hash['res.users']['login'].push(login);
-                //regex_res = regex_login.exec(string);
-            //}
-            /* document link with name_get: [res.model,name] */
-            /* internal link with id: [res.model,id], or [res.model,id|display_name] */
-            //var regex_intlink = new RegExp(/(^|\s)#(\w*[a-zA-Z_]+\w*)\.(\w+[a-zA-Z_]+\w*),(\w+)/g);
-            //regex_res = regex_intlink.exec(string);
-            //while (regex_res != null) {
-                //var res_model = regex_res[2] + '.' + regex_res[3];
-                //var res_name = regex_res[4];
-                //if (! (res_model in this.map_hash)) { this.map_hash[res_model]['name'] = []; }
-                //this.map_hash[res_model]['name'].push(res_name);
-                //regex_res = regex_intlink.exec(string);
-            //}
-        },
-        
-        /**
-         * Updates the mapping; check for to_fetch_ids for each recorded
-         * model, and perform a name_get to update the mapping.
-         * CURRENTLY NOT IMPLEMENTED */
-        do_update_name_get_mapping: function(mapping) {
-        },
     };
 
 
@@ -273,8 +95,7 @@ openerp.mail = function(session) {
      * ------------------------------------------------------------
      * 
      * This widget handles the display of a form to compose a new message.
-     * This form is an OpenERP form_view, build on a mail.compose.message
-     * wizard.
+     * This form is a mail.compose.message form_view.
      */
 
     mail.ComposeMessage = session.web.Widget.extend({
@@ -282,96 +103,52 @@ openerp.mail = function(session) {
         
         /**
          * @param {Object} parent parent
-         * @param {Object} [params]
-         * @param {String} [params.res_model] res_model of document [REQUIRED]
-         * @param {Number} [params.res_id] res_id of record [REQUIRED]
-         * @param {Number} [params.email_mode] true/false, tells whether
-         *      we are in email sending mode
-         * @param {Number} [params.formatting] true/false, tells whether
-         *      we are in advance formatting mode
-         * @param {String} [params.model] mail.compose.message.mode (see
-         *      composition wizard)
-         * @param {Number} [params.msg_id] id of a message in case we are in
-         *      reply mode
+         * @param {Object} [options]
+         * @param {Object} [options.context] context passed to the
+         *  mail.compose.message DataSetSearch. Please refer to this model
+         *  for more details about fields and default values.
          */
-        init: function(parent, params) {
+        init: function (parent, options) {
             var self = this;
             this._super(parent);
             // options
-            this.params = params || {};
-            this.params.context = params.context || {};
-            this.params.email_mode = params.email_mode || false;
-            this.params.formatting = params.formatting || false;
-            this.params.mode = params.mode || 'comment';
-            this.params.form_xml_id = params.form_xml_id || 'email_compose_message_wizard_form_chatter';
-            this.params.form_view_id = false;
-            if (this.params.mode == 'reply') {
-                this.params.active_id = this.params.msg_id;
-            } else {
-                this.params.active_id = this.params.res_id;
-            }
-            this.email_mode = false;
-            this.formatting = false;
-        },
-
-        /**
-         * Reinitialize the widget field values to the default values. The
-         * purpose is to avoid to destroy and re-build a form view. Default
-         * values are therefore given as for an onchange. */
-        reinit: function() {
-            var self = this;
-            if (! this.form_view) return;
-            var call_defer = this.ds_compose.call('default_get', [['subject', 'body_text', 'body_html', 'dest_partner_ids'], this.ds_compose.get_context()]).then(
-                function (result) {
-                    self.form_view.on_processed_onchange({'value': result}, []);
-                });
-            return call_defer;
+            this.options = options || {};
+            this.options.context = options.context || {};
+            this.options.form_xml_id = options.form_xml_id || 'email_compose_message_wizard_form_chatter';
+            this.options.form_view_id = options.form_view_id || false;
+            // debug
+            // console.groupCollapsed('New ComposeMessage: model', this.options.context.default_res_model, ', id', this.options.context.default_res_id);
+            // console.log('context:', this.options.context);
+            // console.groupEnd();
         },
 
-        /**
-         * Override-hack of do_action: clean the form */
-        do_action: function(action, on_close) {
-            // this.init_comments();
-            return this._super(action, on_close);
-        },
-
-        /**
-         * Widget start function
-         * - builds and initializes the form view */
-        start: function() {
-            var self = this;
+        start: function () {
             this._super.apply(this, arguments);
             // customize display: add avatar, clean previous content
-            var user_avatar = mail.ChatterUtils.get_image(this.session.prefix,
-                this.session.session_id, 'res.users', 'image_small', this.session.uid);
+            var user_avatar = mail.ChatterUtils.get_image(this.session.prefix, this.session.session_id, 'res.users', 'image_small', this.session.uid);
             this.$el.find('img.oe_mail_icon').attr('src', user_avatar);
             this.$el.find('div.oe_mail_msg_content').empty();
-            // create a context for the default_get of the compose form
-            var widget_context = {
-                'active_model': this.params.res_model,
-                'active_id': this.params.active_id,
-                'mail.compose.message.mode': this.params.mode,
-            };
-            var context = _.extend({}, this.params.context, widget_context);
+            // create a context for the dataset and default_get of the wizard
+            var context = _.extend({}, this.options.context);
             this.ds_compose = new session.web.DataSetSearch(this, 'mail.compose.message', context);
             // find the id of the view to display in the chatter form
-            var data_ds = new session.web.DataSetSearch(this, 'ir.model.data');
-            var deferred_form_id =data_ds.call('get_object_reference', ['mail', this.params.form_xml_id]).then( function (result) {
-                if (result) {
-                    self.params.form_view_id = result[1];
-                }
-            }).pipe(this.proxy('create_form_view'));
-            return deferred_form_id;
+            if (this.options.form_view_id) {
+                return this.create_form_view();
+            }
+            else {
+                var data_ds = new session.web.DataSetSearch(this, 'ir.model.data');
+                return data_ds.call('get_object_reference', ['mail', this.options.form_xml_id]).pipe(this.proxy('create_form_view'));
+            }
         },
 
-        /**
-         * Create a FormView, then append it to the to widget DOM. */
-        create_form_view: function () {
+        /** Create a FormView, then append it to the to widget DOM. */
+        create_form_view: function (new_form_view_id) {
             var self = this;
+            this.options.form_view_id = (new_form_view_id && new_form_view_id[1]) || this.options.form_view_id;
             // destroy previous form_view if any
             if (this.form_view) { this.form_view.destroy(); }
             // create the FormView
-            this.form_view = new session.web.FormView(this, this.ds_compose, this.params.form_view_id, {
+            this.form_view = new session.web.FormView(this, this.ds_compose, this.options.form_view_id, {
                 action_buttons: false,
                 pager: false,
                 initial_mode: 'edit',
@@ -382,13 +159,24 @@ openerp.mail = function(session) {
             return $.when(this.form_view.appendTo(msg_node)).pipe(function() {
                 self.bind_events();
                 self.form_view.do_show();
-                if (self.params.email_mode) { self.toggle_email_mode(); }
-                if (self.params.formatting) { self.toggle_formatting_mode(); }
             });
         },
 
-        destroy: function() {
-            this._super.apply(this, arguments);
+        /**
+         * Reinitialize the widget field values to the default values obtained
+         * using default_get on mail.compose.message. This allows to reinitialize
+         * the widget without having to rebuild a complete form view.
+         * @param {Object} new_context: context of the refresh */
+        refresh: function (new_context) {
+            if (! this.form_view) return;
+            var self = this;
+            this.options.context = _.extend(this.options.context, new_context || {});
+            this.ds_compose.context = _.extend(this.ds_compose.context, this.options.context);
+            return this.ds_compose.call('default_get', [
+                ['subject', 'body_text', 'body', 'attachment_ids', 'partner_ids', 'composition_mode',
+                    'model', 'res_id', 'parent_id', 'content_subtype'],
+                this.ds_compose.get_context(),
+            ]).then( function (result) { self.form_view.on_processed_onchange({'value': result}, []); });
         },
 
         /**
@@ -396,87 +184,12 @@ openerp.mail = function(session) {
          * in the function. */
         bind_events: function() {
             var self = this;
-            this.$el.find('button.oe_form_button').click(function (event) {
-                event.preventDefault();
-            });
-            // event: click on 'Send an Email' link that toggles the form for
-            // sending an email (partner_ids)
-            this.$el.find('a.oe_mail_compose_message_email').click(function (event) {
-                event.preventDefault();
-                self.toggle_email_mode();
-            });
-            // event: click on 'Formatting' icon-link that toggles the advanced
-            // formatting options for writing a message (subject, body_html)
-            this.$el.find('a.oe_mail_compose_message_formatting').click(function (event) {
-                event.preventDefault();
-                self.toggle_formatting_mode();
-            });
             // event: click on 'Attachment' icon-link that opens the dialog to
             // add an attachment.
-            this.$el.find('a.oe_mail_compose_message_attachment').click(function (event) {
-                event.preventDefault();
-                // not yet implemented
-                self.set_body_value('attachment', 'attachment');
-            });
-            // event: click on 'Checklist' icon-link that toggles the options
-            // for adding checklist.
-            this.$el.find('a.oe_mail_compose_message_checklist').click(function (event) {
-                event.preventDefault();
-                // not yet implemented
-                self.set_body_value('checklist', 'checklist');
+            this.$el.on('click', 'button.oe_mail_compose_message_attachment', function (event) {
+                event.stopImmediatePropagation();
             });
         },
-
-        /**
-         * Toggle the formatting mode. */
-        toggle_formatting_mode: function() {
-            var self = this;
-            this.formatting = ! this.formatting;
-            // calls onchange
-            var call_defer = this.ds_compose.call('onchange_formatting', [[], this.formatting, this.params.res_model, this.params.res_id]).then(
-                function (result) {
-                    self.form_view.on_processed_onchange(result, []);
-                });
-            // update context of datasetsearch
-            this.ds_compose.context.formatting = this.formatting;
-            // toggle display
-            this.$el.find('span.oe_mail_compose_message_subject').toggleClass('oe_mail_compose_message_invisible');
-            this.$el.find('div.oe_mail_compose_message_body_text').toggleClass('oe_mail_compose_message_invisible');
-            this.$el.find('div.oe_mail_compose_message_body_html').toggleClass('oe_mail_compose_message_invisible');
-        },
-
-        /**
-         * Toggle the email mode. */
-        toggle_email_mode: function() {
-            var self = this;
-            this.email_mode = ! this.email_mode;
-            // calls onchange
-            var call_defer = this.ds_compose.call('onchange_email_mode', [[], this.email_mode, this.params.res_model, this.params.res_id]).then(
-                function (result) {
-                    self.form_view.on_processed_onchange(result, []);
-                });
-            // update context of datasetsearch
-            this.ds_compose.context.email_mode = this.email_mode;
-            // update 'Post' button -> 'Send'
-            // update 'Send an Email' link -> 'Post a comment'
-            if (this.email_mode) {
-                this.$el.find('button.oe_mail_compose_message_button_send').html('<span>Send</span>');
-                this.$el.find('a.oe_mail_compose_message_email').html('Comment');
-            } else {
-                this.$el.find('button.oe_mail_compose_message_button_send').html('<span>Post</span>');
-                this.$el.find('a.oe_mail_compose_message_email').html('Send an Email');
-            }
-            // toggle display
-            this.$el.find('div.oe_mail_compose_message_partner_ids').toggleClass('oe_mail_compose_message_invisible');
-        },
-
-        /**
-         * Update the values of the composition form; with possible different
-         * values for body_text and body_html. */
-        set_body_value: function(body_text, body_html) {
-            this.form_view.fields.body_text.set_value(body_text);
-            this.form_view.fields.body_html.set_value(body_html);
-        },
     }),
 
     /** 
@@ -490,9 +203,6 @@ openerp.mail = function(session) {
      * - - sub message (parent_id = root message)
      * - - - sub sub message (parent id = sub message)
      * - - sub message (parent_id = root message)
-     * This widget has 2 ways of initialization, either you give records
-     * to be rendered, either it will fetch [limit] messages related to
-     * [res_model]:[res_id].
      */
 
     mail.Thread = session.web.Widget.extend({
@@ -500,95 +210,67 @@ openerp.mail = function(session) {
 
         /**
          * @param {Object} parent parent
-         * @param {Object} [params]
-         * @param {String} [params.res_model] res_model of document [REQUIRED]
-         * @param {Number} [params.res_id] res_id of record [REQUIRED]
-         * @param {Number} [params.uid] user id [REQUIRED]
-         * @param {Bool}   [params.parent_id=false] parent_id of message
-         * @param {Number} [params.thread_level=0] number of levels in the thread
-         *      (only 0 or 1 currently)
-         * @param {Bool}   [params.is_wall=false] thread is displayed in the wall
-         * @param {Number} [params.msg_more_limit=150] number of character to
+         * @param {Object} [options]
+         * @param {Object} [options.context] context of the thread. It should
+            contain at least default_model, default_res_id. Please refer to
+            the ComposeMessage widget for more information about it.
+         * @param {Number} [options.thread_level=0] number of thread levels
+         * @param {Number} [options.message_ids=null] ids for message_fetch
+         * @param {Number} [options.message_data=null] already formatted message
+            data, for subthreads getting data from their parent
+         * @param {Boolean} [options.composer] use the advanced composer, or
+            the default basic textarea if not set
+         * @param {Number} [options.truncate_limit=250] number of character to
          *      display before having a "show more" link; note that the text
          *      will not be truncated if it does not have 110% of the parameter
-         *      (ex: 110 characters needed to be truncated and be displayed as
-         *      a 100-characters message)
-         * @param {Number} [params.limit=100] maximum number of messages to fetch
-         * @param {Number} [params.offset=0] offset for fetching messages
-         * @param {Number} [params.records=null] records to show instead of fetching messages
          */
-        init: function(parent, params) {
+        init: function(parent, options) {
             this._super(parent);
             // options
-            this.params = params;
-            this.params.parent_id = this.params.parent_id || false;
-            this.params.thread_level = this.params.thread_level || 0;
-            this.params.is_wall = this.params.is_wall || (this.params.records != undefined) || false;
-            this.params.msg_more_limit = this.params.msg_more_limit || 250;
-            this.params.limit = this.params.limit || 100;
-            // this.params.limit = 3; // tmp for testing
-            this.params.offset = this.params.offset || 0;
-            this.params.records = this.params.records || null;
+            this.options = options || {};
+            this.options.domain = options.domain || [];
+            this.options.context = _.extend({
+                default_model: 'mail.thread',
+                default_res_id:  0,
+                default_parent_id: false }, options.context || {});
+            this.options.thread_level = options.thread_level || 0;
+            this.options.composer = options.composer || false;
+            this.options.message_ids = options.message_ids || null;
+            this.options.message_data = options.message_data || null;
             // datasets and internal vars
-            this.ds = new session.web.DataSetSearch(this, this.params.res_model);
-            this.ds_users = new session.web.DataSetSearch(this, 'res.users');
-            this.ds_msg = new session.web.DataSetSearch(this, 'mail.message');
-            this.comments_structure = {'root_ids': [], 'new_root_ids': [], 'msgs': {}, 'tree_struct': {}, 'model_to_root_ids': {}};
+            this.ds_thread = new session.web.DataSetSearch(this, this.options.context.default_model);
+            this.ds_notification = new session.web.DataSetSearch(this, 'mail.notification');
+            this.ds_message = new session.web.DataSetSearch(this, 'mail.message');
             // display customization vars
-            this.display = {};
-            this.display.show_post_comment = this.params.show_post_comment || false;
-            this.display.show_reply = (this.params.thread_level > 0 && this.params.is_wall);
-            this.display.show_delete = ! this.params.is_wall;
-            this.display.show_hide = this.params.is_wall;
-            this.display.show_reply_by_email = ! this.params.is_wall;
-            this.display.show_more = (this.params.thread_level == 0);
+            this.display = {
+                truncate_limit: options.truncate_limit || 250,
+                show_header_compose: options.show_header_compose || false,
+                show_reply: options.show_reply || false,
+                show_delete: options.show_delete || false,
+                show_hide: options.show_hide || false,
+                show_reply_by_email: options.show_reply_by_email || false,
+                show_more: options.show_more || false,
+            }
         },
         
         start: function() {
+            // TDE TODO: check for deferred, not sure it is correct
             this._super.apply(this, arguments);
-            // add events
             this.bind_events();
-            // display user, fetch comments
-            this.display_current_user();
-            if (this.params.records) var display_done = this.display_comments_from_parameters(this.params.records);
-            else var display_done = this.init_comments();
-            // customize display
-            $.when(display_done).then(this.proxy('do_customize_display'));            
+            // fetch and display message, using message_ids if set
+            var display_done = $.when(this.message_fetch(true, [], {})).then(this.proxy('do_customize_display'));
             // add message composition form view
-            if (this.display.show_post_comment) {
+            if (this.display.show_header_compose && this.options.composer) {
                 var compose_done = this.instantiate_composition_form();
             }
             return display_done && compose_done;
         },
 
-        /**
-         * Override-hack of do_action: automatically reload the chatter.
-         * Normally it should be called only when clicking on 'Post/Send'
-         * in the composition form. */
-        do_action: function(action, on_close) {
-            this.init_comments();
-            if (this.compose_message_widget) {
-                this.compose_message_widget.reinit(); }
-            return this._super(action, on_close);
-        },
-
-        instantiate_composition_form: function(mode, email_mode, formatting, msg_id, context) {
-            if (this.compose_message_widget) {
-                this.compose_message_widget.destroy();
-            }
-            this.compose_message_widget = new mail.ComposeMessage(this, {
-                'extended_mode': false, 'uid': this.params.uid, 'res_model': this.params.res_model,
-                'res_id': this.params.res_id, 'mode': mode || 'comment', 'msg_id': msg_id,
-                'email_mode': email_mode || false, 'formatting': formatting || false,
-                'context': context || false } );
-            var composition_node = this.$el.find('div.oe_mail_thread_action');
-            composition_node.empty();
-            var compose_done = this.compose_message_widget.appendTo(composition_node);
-            return compose_done;
-        },
-
+        /** Customize the display
+         * - show_header_compose: show the composition form in the header */
         do_customize_display: function() {
-            if (this.display.show_post_comment) { this.$el.find('div.oe_mail_thread_action').eq(0).show(); }
+            this.display_user_avatar();
+            if (this.display.show_header_compose) { this.$el.find('div.oe_mail_thread_action').eq(0).show(); }
         },
 
         /**
@@ -596,171 +278,183 @@ openerp.mail = function(session) {
          * in the function. */
         bind_events: function() {
             var self = this;
-            // generic events from Chatter Mixin
-            mail.ChatterUtils.bind_events(this);
             // event: click on 'more' at bottom of thread
             this.$el.find('button.oe_mail_button_more').click(function () {
-                self.do_more();
+                self.do_message_fetch();
             });
             // event: writing in basic textarea of composition form (quick reply)
             this.$el.find('textarea.oe_mail_compose_textarea').keyup(function (event) {
                 var charCode = (event.which) ? event.which : window.event.keyCode;
                 if (event.shiftKey && charCode == 13) { this.value = this.value+"\n"; }
-                else if (charCode == 13) { return self.do_comment(); }
+                else if (charCode == 13) { return self.message_post(); }
             });
             // event: click on 'Reply' in msg
-            this.$el.find('div.oe_mail_thread_display').delegate('a.oe_mail_msg_reply', 'click', function (event) {
-                var act_dom = $(this).parents('div.oe_mail_thread_display').find('div.oe_mail_thread_action:first');
-                act_dom.toggle();
+            this.$el.on('click', 'a.oe_mail_msg_reply', function (event) {
                 event.preventDefault();
+                event.stopPropagation();
+                var act_dom = $(this).parents('li.oe_mail_thread_msg').eq(0).find('div.oe_mail_thread_action:first');
+                act_dom.toggle();
             });
             // event: click on 'attachment(s)' in msg
-            this.$el.delegate('a.oe_mail_msg_view_attachments', 'click', function (event) {
+            this.$el.on('click', 'a.oe_mail_msg_view_attachments', function (event) {
+                event.preventDefault();
+                event.stopPropagation();
                 var act_dom = $(this).parent().parent().parent().find('.oe_mail_msg_attachments');
                 act_dom.toggle();
-                event.preventDefault();
             });
             // event: click on 'Delete' in msg side menu
-            this.$el.find('div.oe_mail_thread_display').delegate('a.oe_mail_msg_delete', 'click', function (event) {
+            this.$el.on('click', 'a.oe_mail_msg_delete', function (event) {
+                event.preventDefault();
+                event.stopPropagation();
                 if (! confirm(_t("Do you really want to delete this message?"))) { return false; }
                 var msg_id = event.srcElement.dataset.id;
                 if (! msg_id) return false;
-                var call_defer = self.ds_msg.unlink([parseInt(msg_id)]);
-                $(event.srcElement).parents('li.oe_mail_thread_msg').eq(0).hide();
-                if (self.params.thread_level > 0) {
-                    $(event.srcElement).parents('.oe_mail_thread').eq(0).hide();
-                }
-                event.preventDefault();
-                return call_defer;
+                $(event.srcElement).parents('li.oe_mail_thread_msg').eq(0).remove();
+                return self.ds_msg.unlink([parseInt(msg_id)]);
             });
             // event: click on 'Hide' in msg side menu
-            this.$el.find('div.oe_mail_thread_display').delegate('a.oe_mail_msg_hide', 'click', function (event) {
-                if (! confirm(_t("Do you really want to hide this thread ?"))) { return false; }
+            this.$el.on('click', 'a.oe_mail_msg_hide', function (event) {
+                event.preventDefault();
+                event.stopPropagation();
                 var msg_id = event.srcElement.dataset.id;
                 if (! msg_id) return false;
-                var call_defer = self.ds.call('message_remove_pushed_notifications', [[self.params.res_id], [parseInt(msg_id)], true]);
-                $(event.srcElement).parents('li.oe_mail_thread_msg').eq(0).hide();
-                if (self.params.thread_level > 0) {
-                    $(event.srcElement).parents('.oe_mail_thread').eq(0).hide();
-                }
-                event.preventDefault();
-                return call_defer;
+                $(event.srcElement).parents('li.oe_mail_thread_msg').eq(0).remove();
+                return self.ds_notif.call('set_message_read', [parseInt(msg_id)]);
             });
-            // event: click on "Reply" in msg side menu (email style)
-            this.$el.find('div.oe_mail_thread_display').delegate('a.oe_mail_msg_reply_by_email', 'click', function (event) {
+            // event: click on "Reply by email" in msg side menu (email style)
+            this.$el.on('click', 'a.oe_mail_msg_reply_by_email', function (event) {
+                event.preventDefault();
+                event.stopPropagation();
                 var msg_id = event.srcElement.dataset.msg_id;
-                var email_mode = (event.srcElement.dataset.type == 'email');
-                var formatting = (event.srcElement.dataset.formatting == 'html');
                 if (! msg_id) return false;
-                self.instantiate_composition_form('reply', email_mode, formatting, msg_id);
-                event.preventDefault();
+                self.compose_message_widget.refresh({
+                    'default_composition_mode': 'reply',
+                    'default_parent_id': parseInt(msg_id),
+                    'default_content_subtype': 'html'} );
             });
         },
-        
-        destroy: function () {
-            this._super.apply(this, arguments);
-        },
-        
-        init_comments: function() {
-            var self = this;
-            this.params.offset = 0;
-            this.comments_structure = {'root_ids': [], 'new_root_ids': [], 'msgs': {}, 'tree_struct': {}, 'model_to_root_ids': {}};
-            this.$el.find('div.oe_mail_thread_display').empty();
-            var domain = this.get_fetch_domain(this.comments_structure);
-            return this.fetch_comments(this.params.limit, this.params.offset, domain).then();
+
+        /**
+         * Override-hack of do_action: automatically reload the chatter.
+         * Normally it should be called only when clicking on 'Post/Send'
+         * in the composition form. */
+        do_action: function(action, on_close) {
+            this.message_clean();
+            this.message_fetch();
+            if (this.compose_message_widget) {
+                this.compose_message_widget.refresh({
+                    'default_composition_mode': 'comment',
+                    'default_parent_id': this.options.default_parent_id,
+                    'default_content_subtype': 'plain'} );
+            }
+            return this._super(action, on_close);
         },
-        
-        fetch_comments: function (limit, offset, domain) {
-            var self = this;
-            var defer = this.ds.call('message_read', [[this.params.res_id], (this.params.thread_level > 0), (this.comments_structure['root_ids']),
-                                    (limit+1) || (this.params.limit+1), offset||this.params.offset, domain||undefined ]).then(function (records) {
-                if (records.length <= self.params.limit) self.display.show_more = false;
-                // else { self.display.show_more = true; records.pop(); }
-                // else { self.display.show_more = true; records.splice(0, 1); }
-                else { self.display.show_more = true; }
-                self.display_comments(records);
-                // TODO: move to customize display
-                if (self.display.show_more == true) self.$el.find('div.oe_mail_thread_more:last').show();
-                else  self.$el.find('div.oe_mail_thread_more:last').hide();
+
+        /** Instantiate the composition form, with every parameters in context
+            or in the widget context. */
+        instantiate_composition_form: function(context) {
+            if (this.compose_message_widget) {
+                this.compose_message_widget.destroy();
+            }
+            this.compose_message_widget = new mail.ComposeMessage(this, {
+                'context': _.extend(context || {}, this.options.context),
             });
-            
-            return defer;
+            var composition_node = this.$el.find('div.oe_mail_thread_action');
+            composition_node.empty();
+            var compose_done = this.compose_message_widget.appendTo(composition_node);
+            return compose_done;
         },
 
-        display_comments_from_parameters: function (records) {
-            if (records.length > 0 && records.length < (records[0].child_ids.length+1) ) this.display.show_more = true;
-            else this.display.show_more = false;
-            var defer = this.display_comments(records);
-            // TODO: move to customize display
-            if (this.display.show_more == true) $('div.oe_mail_thread_more').eq(-2).show();
-            else $('div.oe_mail_thread_more').eq(-2).hide();
-            return defer;
+        /** Clean the thread */
+        message_clean: function() {
+            this.$el.find('div.oe_mail_thread_display').empty();
         },
-        
-        display_comments: function (records) {
+
+        /** Fetch messages
+         * @param {Bool} initial_mode: initial mode: try to use message_data or
+         *  message_ids, if nothing available perform a message_read; otherwise
+         *  directly perform a message_read
+         * @param {Array} additional_domain: added to options.domain
+         * @param {Object} additional_context: added to options.context
+         */
+        message_fetch: function (initial_mode, additional_domain, additional_context) {
             var self = this;
-            // sort the records
-            mail.ChatterUtils.records_struct_add_records(this.comments_structure, records, this.params.parent_id);
-            //build attachments download urls and compute time-relative from dates
-            for (var k in records) {
-                records[k].timerelative = $.timeago(records[k].date);
-                if (records[k].attachments) {
-                    for (var l in records[k].attachments) {
-                        var url = self.session.origin + '/web/binary/saveas?session_id=' + self.session.session_id + '&model=ir.attachment&field=datas&filename_field=datas_fname&id='+records[k].attachments[l].id;
-                        records[k].attachments[l].url = url;
-                    }
-                }
+            // domain and context: options + additional
+            fetch_domain = _.flatten([this.options.domain, additional_domain || []], true)
+            fetch_context = _.extend(this.options.context, additional_context || {})
+            // if message_ids is set: try to use it
+            if (initial_mode && this.options.message_data) {
+                return this.message_display(this.options.message_data);
             }
+            return this.ds_message.call('message_read',
+                [(initial_mode && this.options.message_ids) || false, fetch_domain, this.options.thread_level, undefined, fetch_context]
+                ).then(this.proxy('message_display'));
+        },
+
+        /* Display a list of records
+         * A specific case is done for 'expandable' messages that are messages
+            displayed under a 'show more' button form
+         */
+        message_display: function (records) {
+            var self = this;
+            var _expendable = false;
             _(records).each(function (record) {
-                var sub_msgs = [];
-                if ((record.parent_id == false || record.parent_id[0] == self.params.parent_id) && self.params.thread_level > 0 ) {
-                    var sub_list = self.comments_structure['tree_struct'][record.id]['direct_childs'];
-                    _(records).each(function (record) {
-                        //if (record.parent_id == false || record.parent_id[0] == self.params.parent_id) return;
-                        if (_.indexOf(sub_list, record.id) != -1) {
-                            sub_msgs.push(record);
-                        }
+                if (record.type == 'expandable') {
+                    _expendable = true;
+                    self.update_fetch_more(true);
+                    self.fetch_more_domain = record.domain;
+                    self.fetch_more_context = record.context;
+                }
+                else {
+                    self.display_record(record);
+                    // if (self.options.thread_level >= 0) {
+                    self.thread = new mail.Thread(self, {
+                        'context': {
+                            'default_model': record.model,
+                            'default_id': record.res_id,
+                            'default_parent_id': record.id },
+                        'message_data': record.child_ids, 'thread_level': self.options.thread_level-1,
+                        'show_header_compose': false, 'show_reply': self.options.thread_level > 1,
+                        'show_hide': self.display.show_hide, 'show_delete': self.display.show_delete,
                     });
-                    self.display_comment(record);
-                    self.thread = new mail.Thread(self, {'res_model': self.params.res_model, 'res_id': self.params.res_id, 'uid': self.params.uid,
-                                                            'records': sub_msgs, 'thread_level': (self.params.thread_level-1), 'parent_id': record.id,
-                                                            'is_wall': self.params.is_wall});
                     self.$el.find('li.oe_mail_thread_msg:last').append('<div class="oe_mail_thread_subthread"/>');
                     self.thread.appendTo(self.$el.find('div.oe_mail_thread_subthread:last'));
-                }
-                else if (self.params.thread_level == 0) {
-                    self.display_comment(record);
+                    // }
                 }
             });
-            mail.ChatterUtils.records_struct_update_after_display(this.comments_structure);
-            // update offset for "More" buttons
-            if (this.params.thread_level == 0) this.params.offset += records.length;
+            if (! _expendable) {
+                this.update_fetch_more(false);
+            }
         },
 
-        /** Displays a record, performs text/link formatting */
-        display_comment: function (record) {
-            record.body = mail.ChatterUtils.do_text_nl2br($.trim(record.body), true);
-            // if (record.type == 'email' && record.state == 'received') {
+        /** Displays a record and performs some formatting on the record :
+         * - record.date: formatting according to the user timezone
+         * - record.timerelative: relative time givein by timeago lib
+         * - record.avatar: image url
+         * - record.attachments[].url: url of each attachment
+         * - record.is_author: is the current user the author of the record */
+        display_record: function (record) {
+            // formatting and additional fields
+            record.date = session.web.format_value(record.date, {type:"datetime"});
+            record.timerelative = $.timeago(record.date);
             if (record.type == 'email') {
-                record.mini_url = ('/mail/static/src/img/email_icon.png');
+                record.avatar = ('/mail/static/src/img/email_icon.png');
             } else {
-                record.mini_url = mail.ChatterUtils.get_image(this.session.prefix, this.session.session_id, 'res.users', 'image_small', record.user_id[0]);
+                record.avatar = mail.ChatterUtils.get_image(this.session.prefix, this.session.session_id, 'res.partner', 'image_small', record.author_id[0]);
             }
-            // body text manipulation
-            if (record.subtype == 'plain') {
-                record.body = mail.ChatterUtils.do_text_remove_html_tags(record.body);
+            //TDE: FIX
+            if (record.attachments) {
+                for (var l in record.attachments) {
+                    var url = self.session.origin + '/web/binary/saveas?session_id=' + self.session.session_id + '&model=ir.attachment&field=datas&filename_field=datas_fname&id='+records[k].attachments[l].id;
+                    record.attachments[l].url = url;
+                }
             }
-            record.body = mail.ChatterUtils.do_replace_expressions(record.body);
-            // format date according to the user timezone
-            record.date = session.web.format_value(record.date, {type:"datetime"});
-            // is the user the author ?
-            record.is_author = mail.ChatterUtils.is_author(this, record.user_id[0]);
-            // render
-            var rendered = session.web.qweb.render('mail.thread.message', {'record': record, 'thread': this, 'params': this.params, 'display': this.display});
-            // expand feature
+            record.is_author = mail.ChatterUtils.is_author(this, record.author_user_id[0]);
+            // render, add the expand feature
+            var rendered = session.web.qweb.render('mail.thread.message', {'record': record, 'thread': this, 'params': this.options, 'display': this.display});
             $(rendered).appendTo(this.$el.children('div.oe_mail_thread_display:first'));
             this.$el.find('div.oe_mail_msg_record_body').expander({
-                slicePoint: this.params.msg_more_limit,
+                slicePoint: this.options.msg_more_limit,
                 expandText: 'read more',
                 userCollapseText: '[^]',
                 detailClass: 'oe_mail_msg_tail',
@@ -769,54 +463,68 @@ openerp.mail = function(session) {
                 });
         },
 
-        display_current_user: function () {
-            var avatar = mail.ChatterUtils.get_image(this.session.prefix, this.session.session_id, 'res.users', 'image_small', this.params.uid);
-            return this.$el.find('img.oe_mail_icon').attr('src', avatar);
+        /** Display 'show more' button */
+        update_fetch_more: function (new_value) {
+            if (new_value) {
+                    this.$el.find('div.oe_mail_thread_more:last').show();
+            } else {
+                    this.$el.find('div.oe_mail_thread_more:last').hide();
+            }
         },
-        
-        do_comment: function () {
-            var comment_node = this.$el.find('textarea');
-            var body_text = comment_node.val();
-            comment_node.val('');
-            return this.ds.call('message_append_note', [[this.params.res_id], '', body_text, this.params.parent_id, 'comment', 'plain']).then(
-                this.proxy('init_comments'));
+
+        display_user_avatar: function () {
+            var avatar = mail.ChatterUtils.get_image(this.session.prefix, this.session.session_id, 'res.users', 'image_small', this.options.uid);
+            return this.$el.find('img.oe_mail_icon').attr('src', avatar);
         },
         
-        /**
-         * Create a domain to fetch new comments according to
-         * comment already present in comments_structure
-         * @param {Object} comments_structure (see chatter utils)
-         * @returns {Array} fetch_domain (OpenERP domain style)
-         */
-        get_fetch_domain: function (comments_structure) {
-            var domain = [];
-            var ids = comments_structure.root_ids.slice();
-            var ids2 = [];
-            // must be child of current parent
-            if (this.params.parent_id) { domain.push(['id', 'child_of', this.params.parent_id]); }
-            _(comments_structure.root_ids).each(function (id) { // each record
-                ids.push(id);
-                ids2.push(id);
-            });
-            if (this.params.parent_id != false) {
-                ids2.push(this.params.parent_id);
-            }
-            // must not be children of already fetched messages
-            if (ids.length > 0) {
-                domain.push('&');
-                domain.push('!');
-                domain.push(['id', 'child_of', ids]);
-            }
-            if (ids2.length > 0) {
-                domain.push(['id', 'not in', ids2]);
+        message_post: function (body) {
+            var self = this;
+            if (! body) {
+                var comment_node = this.$el.find('textarea');
+                var body = comment_node.val();
+                comment_node.val('');
             }
-            return domain;
-        },
-        
-        do_more: function () {
-            domain = this.get_fetch_domain(this.comments_structure);
-            return this.fetch_comments(this.params.limit, this.params.offset, domain);
-        },
+            return this.ds_post.call('message_post', [
+                [this.options.context.res_id], body, false, 'comment', this.options.context.parent_id]
+                ).then(this.proxy('message_fetch'));
+        },
+
+        /** Action: 'shows more' to fetch new messages */
+        do_message_fetch: function () {
+            return this.message_fetch(false, this.fetch_more_domain, this.fetch_more_context);
+        },
+
+        // TDE: keep currently because need something similar
+        // /**
+        //  * Create a domain to fetch new comments according to
+        //  * comment already present in comments_structure
+        //  * @param {Object} comments_structure (see chatter utils)
+        //  * @returns {Array} fetch_domain (OpenERP domain style)
+        //  */
+        // get_fetch_domain: function (comments_structure) {
+        //     var domain = [];
+        //     var ids = comments_structure.root_ids.slice();
+        //     var ids2 = [];
+        //     // must be child of current parent
+        //     if (this.options.parent_id) { domain.push(['id', 'child_of', this.options.parent_id]); }
+        //     _(comments_structure.root_ids).each(function (id) { // each record
+        //         ids.push(id);
+        //         ids2.push(id);
+        //     });
+        //     if (this.options.parent_id != false) {
+        //         ids2.push(this.options.parent_id);
+        //     }
+        //     // must not be children of already fetched messages
+        //     if (ids.length > 0) {
+        //         domain.push('&');
+        //         domain.push('!');
+        //         domain.push(['id', 'child_of', ids]);
+        //     }
+        //     if (ids2.length > 0) {
+        //         domain.push(['id', 'not in', ids2]);
+        //     }
+        //     return domain;
+        // },
     });
 
 
@@ -833,62 +541,60 @@ openerp.mail = function(session) {
 
     /** mail_thread widget: thread of comments */
     mail.RecordThread = session.web.form.AbstractField.extend({
-        // QWeb template to use when rendering the object
         template: 'mail.record_thread',
 
-       init: function() {
+        init: function() {
             this._super.apply(this, arguments);
-            this.params = this.options;
-            this.params.thread_level = this.params.thread_level || 0;
-            this.thread = null;
-            this.ds = new session.web.DataSet(this, this.view.model);
-            this.ds_users = new session.web.DataSet(this, 'res.users');
+            this.options.domain = this.options.domain || [];
+            this.options.context = {'default_model': 'mail.thread', 'default_res_id': false};
+            this.options.thread_level = this.options.thread_level || 0;
+            this.thread_list = [];
         },
 
         start: function() {
+            this._super.apply(this, arguments);
             // NB: all the widget should be modified to check the actual_mode property on view, not use
             // any other method to know if the view is in create mode anymore
             this.view.on("change:actual_mode", this, this._check_visibility);
             this._check_visibility();
-            mail.ChatterUtils.bind_events(this);
-            this.$el.find('button.oe_mail_button_followers').click(function () { self.do_toggle_followers(); });
-            if (! this.params.see_subscribers_options) {
-                this.$el.find('button.oe_mail_button_followers').hide(); }
-            this.$el.find('button.oe_mail_button_follow').click(function () { self.do_follow(); });
-            this.$el.find('button.oe_mail_button_unfollow').click(function () { self.do_unfollow(); })
-                .mouseover(function () { $(this).html('Unfollow').removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover'); })
-                .mouseleave(function () { $(this).html('Following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); });
         },
-        
+
         _check_visibility: function() {
             this.$el.toggle(this.view.get("actual_mode") !== "create");
         },
-        
-        destroy: function () {
+
+        destroy: function() {
+            for (var i in this.thread_list) { this.thread_list[i].destroy(); }
             this._super.apply(this, arguments);
         },
-        
+
         set_value: function() {
+            var self = this;
             this._super.apply(this, arguments);
-            if (! this.view.datarecord.id ||
-                session.web.BufferedDataSet.virtual_id_regex.test(this.view.datarecord.id)) {
-                this.$el.find('.oe_mail_thread').hide();
+            if (! this.view.datarecord.id || session.web.BufferedDataSet.virtual_id_regex.test(this.view.datarecord.id)) {
+                this.$el.find('oe_mail_thread').hide();
                 return;
             }
+            // update context
+            _.extend(this.options.context, {
+                default_res_id: this.view.datarecord.id,
+                default_model: this.view.model });
             // create and render Thread widget
             this.$el.find('div.oe_mail_recthread_main').empty();
-            if (this.thread) this.thread.destroy();
-            this.thread = new mail.Thread(this, {'res_model': this.view.model, 'res_id': this.view.datarecord.id, 'uid': this.session.uid,
-                                                'thread_level': this.params.thread_level, 'show_post_comment': true, 'limit': 15});
-            var thread_done = this.thread.appendTo(this.$el.find('div.oe_mail_recthread_main'));
-            return thread_done;
+            for (var i in this.thread_list) { this.thread_list[i].destroy(); }
+            var thread = new mail.Thread(self, {
+                'context': this.options.context,
+                'thread_level': this.options.thread_level, 'show_header_compose': true,
+                'show_delete': true, 'composer': true });
+            this.thread_list.push(thread);
+            return thread.appendTo(this.$el.find('div.oe_mail_recthread_main'));
         },
     });
 
 
     /** 
      * ------------------------------------------------------------
-     * WallView Widget
+     * Wall Widget
      * ------------------------------------------------------------
      *
      * This widget handles the display of the Chatter on the Wall.
@@ -903,92 +609,45 @@ openerp.mail = function(session) {
 
         /**
          * @param {Object} parent parent
-         * @param {Object} [params]
-         * @param {Number} [params.limit=20] number of messages to show and fetch
-         * @param {Number} [params.search_view_id=false] search view id for messages
-         * @var {Array} comments_structure (see chatter utils)
+         * @param {Object} [options]
+         * @param {Number} [options.domain] domain on the Wall, is an array.
+         * @param {Number} [options.domain] context, is an object. It should
+         *      contain default_model, default_res_id, to give it to the threads.
          */
-        init: function (parent, params) {
+        init: function (parent, options) {
             this._super(parent);
-            this.params = {};
-            this.params.limit = params.limit || 25;
-            this.params.domain = params.domain || [];
-            this.params.context = params.context || {};
-            this.params.res_model = params.res_model || false;
-            this.params.res_id = params.res_id || false;
-            this.params.search_view_id = params.search_view_id || false;
-            this.params.thread_level = params.thread_level || 1;
-            this.comments_structure = {'root_ids': [], 'new_root_ids': [], 'msgs': {}, 'tree_struct': {}, 'model_to_root_ids': {}};
-            this.display_show_more = true;
+            this.options = options || {};
+            this.options.domain = options.domain || [];
+            this.options.context = options.context || {};
+            this.options.thread_level = options.thread_level || 1;
             this.thread_list = [];
+            this.ds_msg = new session.web.DataSetSearch(this, 'mail.message');
+            // for search view
             this.search = {'domain': [], 'context': {}, 'groupby': {}}
             this.search_results = {'domain': [], 'context': {}, 'groupby': {}}
-            // datasets
-            this.ds_msg = new session.web.DataSet(this, 'mail.message');
-            this.ds_thread = new session.web.DataSet(this, 'mail.thread');
-            this.ds_users = new session.web.DataSet(this, 'res.users');
         },
 
         start: function () {
             this._super.apply(this, arguments);
-            this.display_current_user();
-            // add events
-            this.add_event_handlers();
-            // load mail.message search view
-            var search_view_ready = this.load_search_view(this.params.search_view_id, {}, false);
-            // load composition form
-            var compose_done = this.instantiate_composition_form();
-            // fetch first threads
-            var comments_ready = this.init_and_fetch_comments(this.params.limit, 0);
-            return (search_view_ready && comments_ready && compose_done);
-        },
-
-        /**
-         * Override-hack of do_action: automatically reload the chatter.
-         * Normally it should be called only when clicking on 'Post/Send'
-         * in the composition form. */
-        do_action: function(action, on_close) {
-            this.init_and_fetch_comments();
-            if (this.compose_message_widget) {
-                this.compose_message_widget.reinit(); }
-            return this._super(action, on_close);
+            var search_view_ready = this.load_search_view({}, false);
+            var thread_displayed = this.message_display();
+            return (search_view_ready && thread_displayed);
         },
 
         destroy: function () {
+            for (var i in this.thread_list) { this.thread_list[i].destroy(); }
             this._super.apply(this, arguments);
         },
 
-        instantiate_composition_form: function(mode, msg_id) {
-            if (this.compose_message_widget) {
-                this.compose_message_widget.destroy();
-            }
-            this.compose_message_widget = new mail.ComposeMessage(this, {
-                'extended_mode': false, 'uid': this.session.uid, 'res_model': this.params.res_model,
-                'res_id': this.params.res_id, 'mode': mode || 'comment', 'msg_id': msg_id });
-            var composition_node = this.$el.find('div.oe_mail_wall_action');
-            composition_node.empty();
-            var compose_done = this.compose_message_widget.appendTo(composition_node);
-            return compose_done;
-        },
-
-        /** Add events */
-        add_event_handlers: function () {
-            var self = this;
-            // display more threads
-            this.$el.find('button.oe_mail_wall_button_more').click(function () { return self.do_more(); });
-        },
-
         /**
-         * Loads the mail.message search view
-         * @param {Number} view_id id of the search view to load
+         * Load the mail.message search view
          * @param {Object} defaults ??
          * @param {Boolean} hidden some kind of trick we do not care here
          */
-        load_search_view: function (view_id, defaults, hidden) {
+        load_search_view: function (defaults, hidden) {
             var self = this;
-            this.searchview = new session.web.SearchView(this, this.ds_msg, view_id || false, defaults || {}, hidden || false);
-            var search_view_loaded = this.searchview.appendTo(this.$el.find('.oe_view_manager_view_search'));
-            return $.when(search_view_loaded).then(function () {
+            this.searchview = new session.web.SearchView(this, this.ds_msg, false, defaults || {}, hidden || false);
+            return this.searchview.appendTo(this.$el.find('.oe_view_manager_view_search')).then(function () {
                 self.searchview.on_search.add(self.do_searchview_search);
             });
         },
@@ -1011,100 +670,30 @@ openerp.mail = function(session) {
                 self.search_results['context'] = results.context;
                 self.search_results['domain'] = results.domain;
                 self.search_results['groupby'] = results.group_by;
-                return self.init_and_fetch_comments();
+                self.message_clean();
+                return self.message_display();
             });
         },
 
-        display_current_user: function () {
-            //return this.$el.find('img.oe_mail_msg_image').attr('src', this.thread_get_avatar('res.users', 'avatar', this.session.uid));
-        }, 
-
-        /**
-         * Initializes the wall and calls fetch_comments
-         * @param {Number} limit: number of notifications to fetch
-         * @param {Number} offset: offset in notifications search
-         * @param {Array} domain
-         * @param {Array} context
-         */
-        init_and_fetch_comments: function() {
-            this.search['domain'] = _.union(this.params.domain, this.search_results.domain);
-            this.search['context'] = _.extend(this.params.context, this.search_results.context);
-            this.display_show_more = true;
-            this.comments_structure = {'root_ids': [], 'new_root_ids': [], 'msgs': {}, 'tree_struct': {}, 'model_to_root_ids': {}};
+        /** Clean the wall */
+        message_clean: function() {
             this.$el.find('ul.oe_mail_wall_threads').empty();
-            return this.fetch_comments(this.params.limit, 0);
-        },
-
-        /**
-         * Fetches wall messages
-         * @param {Number} limit: number of notifications to fetch
-         * @param {Number} offset: offset in notifications search
-         * @param {Array} domain
-         * @param {Array} context
-         */
-        fetch_comments: function (limit, offset, additional_domain, additional_context) {
-            var self = this;
-            if (additional_domain) var fetch_domain = this.search['domain'].concat(additional_domain);
-            else var fetch_domain = this.search['domain'];
-            if (additional_context) var fetch_context = _.extend(this.search['context'], additional_context);
-            else var fetch_context = this.search['context'];
-            return this.ds_thread.call('message_get_pushed_messages', 
-                [[this.session.uid], true, [], (limit || 0), (offset || 0), fetch_domain, fetch_context]).then(this.proxy('display_comments'));
         },
 
-        /**
-         * @param {Array} records records to show in threads
-         */
-        display_comments: function (records) {
-            var self = this;
-            this.do_update_show_more(records.length >= self.params.limit);
-            mail.ChatterUtils.records_struct_add_records(this.comments_structure, records, false);
-            _(this.comments_structure['new_root_ids']).each(function (root_id) {
-                var records = self.comments_structure.tree_struct[root_id]['for_thread_msgs'];
-                var model_name = self.comments_structure.msgs[root_id]['model'];
-                var res_id = self.comments_structure.msgs[root_id]['res_id'];
-                var render_res = session.web.qweb.render('mail.wall_thread_container', {});
-                $('<li class="oe_mail_wall_thread">').html(render_res).appendTo(self.$el.find('ul.oe_mail_wall_threads'));
-                var thread = new mail.Thread(self, {
-                    'res_model': model_name, 'res_id': res_id, 'uid': self.session.uid, 'records': records,
-                    'parent_id': false, 'thread_level': self.params.thread_level, 'show_hide': true, 'is_wall': true}
-                    );
-                self.thread_list.push(thread);
-                return thread.appendTo(self.$el.find('li.oe_mail_wall_thread:last'));
-            });
-            // update TODO
-            this.comments_structure['root_ids'] = _.union(this.comments_structure['root_ids'], this.comments_structure['new_root_ids']);
-            this.comments_structure['new_root_ids'] = [];
-        },
-
-        /**
-         * Create a domain to fetch new comments according to
-         * comments already present in comments_structure
-         * - for each model:
-         * -- should not be child of already displayed ids
-         * @returns {Array} fetch_domain (OpenERP domain style)
-         */
-        get_fetch_domain: function () {
-            var self = this;
-            var model_to_root = {};
-            var fetch_domain = [];
-            _(this.comments_structure['model_to_root_ids']).each(function (sc_model, model_name) {
-                fetch_domain.push('|', ['model', '!=', model_name], '!', ['id', 'child_of', sc_model]);
-            });
-            return fetch_domain;
-        },
-        
-        /** Display update: show more button */
-        do_update_show_more: function (new_value) {
-            if (new_value != undefined) this.display_show_more = new_value;
-            if (this.display_show_more) this.$el.find('div.oe_mail_wall_more:last').show();
-            else this.$el.find('div.oe_mail_wall_more:last').hide();
-        },
-        
-        /** Action: Shows more discussions */
-        do_more: function () {
-            var domain = this.get_fetch_domain();
-            return this.fetch_comments(this.params.limit, 0, domain);
+        /** Display the Wall threads */
+        message_display: function () {
+            var render_res = session.web.qweb.render('mail.wall_thread_container', {});
+            $('<li class="oe_mail_wall_thread">').html(render_res).appendTo(this.$el.find('ul.oe_mail_wall_threads'));
+            var thread = new mail.Thread(this, {
+                'domain': this.options.domain, 'context': this.options.context,
+                'thread_level': this.options.thread_level, 'composer': true,
+                // display options
+                'show_header_compose': true,  'show_reply': this.options.thread_level > 0,
+                'show_hide': true, 'show_reply_by_email': true,
+                }
+            );
+            thread.appendTo(this.$el.find('li.oe_mail_wall_thread:last'));
+            this.thread_list.push(thread);
         },
     });
 };
index eda5623..e5d216e 100644 (file)
@@ -9,7 +9,7 @@ openerp_mail_followers = function(session, mail) {
      * mail_followers Widget
      * ------------------------------------------------------------
      *
-     * This widget handles the display of a list of records as a vetical
+     * This widget handles the display of a list of records as a vertical
      * list, with an image on the left. The widget itself is a floatting
      * right-sided box.
      * This widget is mainly used to display the followers of records
@@ -24,12 +24,8 @@ openerp_mail_followers = function(session, mail) {
 
         init: function() {
             this._super.apply(this, arguments);
-            this.params = {};
-            this.params.image = this.node.attrs.image || 'image_small';
-            this.params.title = this.node.attrs.title || 'Followers';
-            this.params.display_followers = true;
-            this.params.display_control = this.node.attrs.display_control || false;
-            this.params.display_actions = this.node.attrs.display_actions || false;
+            this.options.image = this.node.attrs.image || 'image_small';
+            this.options.title = this.node.attrs.title || 'Followers';
             this.ds_model = new session.web.DataSetSearch(this, this.view.model);
             this.ds_follow = new session.web.DataSetSearch(this, this.field.relation);
         },
@@ -40,9 +36,6 @@ openerp_mail_followers = function(session, mail) {
             // any other method to know if the view is in create mode anymore
             this.view.on("change:actual_mode", this, this._check_visibility);
             this._check_visibility();
-            this.$el.find('button.oe_mail_button_followers').click(function () { self.do_toggle_followers(); });
-            if (! this.params.display_control) {
-                this.$el.find('button.oe_mail_button_followers').hide(); }
             this.$el.find('button.oe_mail_button_follow').click(function () { self.do_follow(); })
                 .mouseover(function () { $(this).html('Follow').removeClass('oe_mail_button_mouseout').addClass('oe_mail_button_mouseover'); })
                 .mouseleave(function () { $(this).html('Not following').removeClass('oe_mail_button_mouseover').addClass('oe_mail_button_mouseout'); });
@@ -61,10 +54,6 @@ openerp_mail_followers = function(session, mail) {
         },
 
         reinit: function() {
-            this.params.display_followers = true;
-            this.params.display_control = this.node.attrs.display_control || false;
-            this.params.display_actions = this.node.attrs.display_actions || false;
-            this.$el.find('button.oe_mail_button_followers').html('Hide followers')
             this.$el.find('button.oe_mail_button_follow').hide();
             this.$el.find('button.oe_mail_button_unfollow').hide();
         },
@@ -76,27 +65,24 @@ openerp_mail_followers = function(session, mail) {
                 this.$el.find('div.oe_mail_recthread_aside').hide();
                 return;
             }
-            return this.fetch_subscribers(value_);
+            return this.fetch_followers(value_);
         },
 
-        fetch_subscribers: function (value_) {
-            return this.ds_follow.call('read', [value_ || this.get_value(), ['name', this.params.image]]).then(this.proxy('display_subscribers'));
+        fetch_followers: function (value_) {
+            return this.ds_follow.call('read', [value_ || this.get_value(), ['name', 'user_ids']]).then(this.proxy('display_followers'));
         },
 
-        /**
-         * Display the followers.
-         * TODO: replace the is_subscriber check by fields read */
-        display_subscribers: function (records) {
+        /** Display the followers, evaluate is_follower directly */
+        display_followers: function (records) {
             var self = this;
-            this.is_subscriber = false;
-            var user_list = this.$el.find('ul.oe_mail_followers_display').empty();
-            this.$el.find('div.oe_mail_recthread_followers h4').html(this.params.title + ' (' + records.length + ')');
+            this.message_is_follower = _.indexOf(_.flatten(_.pluck(records, 'user_ids')), this.session.uid) != -1;
+            var node_user_list = this.$el.find('ul.oe_mail_followers_display').empty();
+            this.$el.find('div.oe_mail_recthread_followers h4').html(this.options.title + ' (' + records.length + ')');
             _(records).each(function (record) {
-                if (record.id == self.session.uid) { self.is_subscriber = true; }
-                record.avatar_url = mail.ChatterUtils.get_image(self.session.prefix, self.session.session_id, 'res.users', 'image_small', record.id);
-                $(session.web.qweb.render('mail.followers.partner', {'record': record})).appendTo(user_list);
+                record.avatar_url = mail.ChatterUtils.get_image(self.session.prefix, self.session.session_id, 'res.partner', 'image_small', record.id);
+                $(session.web.qweb.render('mail.followers.partner', {'record': record})).appendTo(node_user_list);
             });
-            if (this.is_subscriber) {
+            if (this.message_is_follower) {
                 this.$el.find('button.oe_mail_button_follow').hide();
                 this.$el.find('button.oe_mail_button_unfollow').show(); }
             else {
@@ -105,18 +91,13 @@ openerp_mail_followers = function(session, mail) {
         },
 
         do_follow: function () {
-            return this.ds_model.call('message_subscribe', [[this.view.datarecord.id]]).pipe(this.proxy('set_value'));
+            var context = new session.web.CompoundContext(this.build_context(), {'read_back': true});
+            return this.ds_model.call('message_subscribe_users', [[this.view.datarecord.id], undefined, context]).pipe(this.proxy('set_value'));
         },
 
         do_unfollow: function () {
-            return this.ds_model.call('message_unsubscribe', [[this.view.datarecord.id]]).pipe(this.proxy('set_value'));
-        },
-
-        do_toggle_followers: function () {
-            this.params.see_subscribers = ! this.params.see_subscribers;
-            if (this.params.see_subscribers) { this.$el.find('button.oe_mail_button_followers').html('Hide followers'); }
-            else { this.$el.find('button.oe_mail_button_followers').html('Show followers'); }
-            this.$el.find('div.oe_mail_recthread_followers').toggle();
+            var context = new session.web.CompoundContext(this.build_context(), {'read_back': true});
+            return this.ds_model.call('message_unsubscribe_users', [[this.view.datarecord.id], undefined, context]).pipe(this.proxy('set_value'));
         },
     });
 };
index 6b5a8b5..f6be765 100644 (file)
@@ -17,8 +17,9 @@
                 <td colspan="2">
                     <h2 class="oe_view_title">
                         <span class="oe_view_title_text">
-                            <t t-if="! widget.params.res_model">News Feeds</t>
-                            <t t-if="widget.params.res_model" t-esc="widget.params.res_model"/>
+                            News Feed
+                            <t t-if="widget.options.res_model"> / </t>
+                            <t t-if="widget.options.res_model" t-esc="widget.options.res_model"/>
                         </span>
                     </h2>
                 </td>
             </tr>
           </tbody>
         </table>
-        <div class="oe_mail_wall_main oe_semantic_html_override">
-            <div class="oe_mail_wall_action">
-                <!-- call the composition form -->
-                <t t-call ="mail.compose_message"/>
-            </div>
-            <div class="oe_clear"/>
-            <ul class="oe_mail_wall_threads">
-                <!-- contains threads -->
-            </ul>
-            <div class="oe_mail_wall_more">
-                <button class="oe_mail_wall_button_more" type="button">See more...</button>
-            </div>
-        </div>
+        <ul class="oe_mail_wall_threads">
+            <!-- contains threads -->
+        </ul>
         <div class="oe_mail_wall_aside">
                 <!-- contains currently nothing -->
         </div>
         Template used to display the communication history in documents
         form view.
         -->
-    <div t-name="mail.record_thread" class="oe_mail_recthread">
+    <div t-name="mail.record_thread">
         <!-- <h4>History and Comments</h4> -->
         <div class="oe_mail_recthread_main">
             <!-- contains the document thread -->
         </div>
-        <div class="oe_mail_recthread_aside">
-            <div class="oe_mail_recthread_actions">
-                <button type="button" class="oe_mail_button_follow">Follow</button>
-                <button type="button" class="oe_mail_button_unfollow oe_mail_button_mouseout">Following</button>
-                <button type="button" class="oe_mail_button_followers">Show followers</button>
-            </div>
-            <div class="oe_mail_recthread_followers">
-                <h4>Followers</h4>
-                <ul class="oe_mail_followers_display"></ul>
-            </div>
-        </div>
     </div>
 
     <!--
     <!-- default layout -->
     <li t-name="mail.thread.message" class="oe_mail oe_mail_thread_msg">
         <div t-attf-class="oe_mail_msg_#{record.type} oe_semantic_html_override">
-            <img class="oe_mail_icon oe_mail_frame oe_left" t-att-src="record.mini_url"/>
+            <img class="oe_mail_icon oe_mail_frame oe_left" t-att-src="record.avatar"/>
             <div class="oe_mail_msg_content">
                 <!-- dropdown menu with message options and actions -->
                 <span class="oe_dropdown_toggle oe_dropdown_arrow">
                     <ul class="oe_dropdown_menu">
-                        <t t-if="display['show_delete']">
-                                <li t-if="record.is_author"><a href="#" class="oe_mail_msg_delete" t-attf-data-id='{record.id}'>Delete</a></li>
+                        <t t-if="record.is_author">
+                            <li t-if="display['show_delete']"><a href="#" class="oe_mail_msg_delete" t-attf-data-id='{record.id}'>Delete</a></li>
                         </t>
-                        <li t-if="display['show_hide']"><a href="#" class="oe_mail_msg_hide" t-attf-data-id='{record.id}'>Hide</a></li>
+                        <li t-if="display['show_hide']"><a href="#" class="oe_mail_msg_hide" t-attf-data-id='{record.id}'>Remove notification</a></li>
 <!--                    Uncomment when adding subtype hiding
                         <li t-if="display['show_hide']">
                             <a href="#" class="oe_mail_msg_hide_type" t-attf-data-subtype='{record.subtype}'>Hide '<t t-esc="record.subtype"/>' for this document</a>
                         </li> -->
-                        <li><a href="#" t-attf-data-msg_id="{record.id}" t-attf-data-type="{record.type}" t-attf-data-formatting="{record.content_subtype}" class="oe_mail_msg_reply_by_email">Reply by email</a></li>
+                        <li><a href="#" t-attf-data-msg_id="{record.id}" class="oe_mail_msg_reply_by_email">Quote and reply</a></li>
                         <li t-if="record.type == 'email'"><a t-attf-href="#model=mail.message&amp;id=#{record.id}" class="oe_mail_msg_details">Details</a></li>
                     </ul>
                 </span>
                     <div class="oe_clear"/>
                     <ul class="oe_mail_msg_footer">
                       <li t-if="record.subject &amp; params.thread_level > 0"><a t-attf-href="#model=#{params.res_model}&amp;id=#{params.res_id}"><t t-raw="record.record_name"/></a></li>
-                      <li><a t-attf-href="#model=res.users&amp;id=#{record.user_id[0]}"><t t-raw="record.user_id[1]"/></a></li>
+                      <li><a t-attf-href="#model=res.partner&amp;id=#{record.author_id[0]}"><t t-raw="record.author_id[1]"/></a></li>
                       <li><span t-att-title="record.date"><t t-raw="record.timerelative"/></span></li>
                       <li t-if="display['show_reply']"><a href="#" class="oe_mail_msg_reply">Reply</a></li>
                       <!-- uncomment when merging vote
index 8292421..39622f4 100644 (file)
@@ -9,11 +9,10 @@
         <div class="oe_mail_recthread_actions">
             <button type="button" class="oe_mail_button_follow oe_mail_button_mouseout">Not following</button>
             <button type="button" class="oe_mail_button_unfollow oe_mail_button_mouseout">Following</button>
-            <button type="button" class="oe_mail_button_followers">Show followers</button>
         </div>
         <div class="oe_mail_recthread_followers">
-            <t t-if="widget.params.title">
-                <h4><t t-raw="widget.params.title"/></h4>
+            <t t-if="widget.options.title">
+                <h4><t t-raw="widget.options.title"/></h4>
             </t>
             <ul class="oe_mail_followers_display"></ul>
         </div>
@@ -25,7 +24,7 @@
         -->
     <li t-name="mail.followers.partner">
         <img class="oe_mail_thumbnail oe_mail_frame" t-attf-src="{record.avatar_url}"/>
-        <a t-attf-href="#model=res.users&amp;id=#{record.id}"><t t-raw="record.name"/></a>
+        <a t-attf-href="#model=res.partner&amp;id=#{record.id}"><t t-raw="record.name"/></a>
     </li>
 
 </template>
index 0940d72..f45745f 100644 (file)
@@ -20,6 +20,7 @@
 ##############################################################################
 
 from openerp.tests import common
+from openerp.tools.html_sanitize import html_sanitize
 
 MAIL_TEMPLATE = """Return-Path: <whatever-2a840@postmaster.twitter.com>
 To: {to}
@@ -62,51 +63,544 @@ Content-Transfer-Encoding: quoted-printable
 ------=_Part_4200734_24778174.1344608186754--
 """
 
+MAIL_TEMPLATE_PLAINTEXT = """Return-Path: <whatever-2a840@postmaster.twitter.com>
+To: {to}
+Received: by mail1.openerp.com (Postfix, from userid 10002)
+    id 5DF9ABFB2A; Fri, 10 Aug 2012 16:16:39 +0200 (CEST)
+From: Sylvie Lelitre <sylvie.lelitre@agrolait.com>
+Subject: {subject}
+MIME-Version: 1.0
+Content-Type: text/plain
+Date: Fri, 10 Aug 2012 14:16:26 +0000
+Message-ID: {msg_id}
+{extra}
+
+Please call me as soon as possible this afternoon!
+
+--
+Sylvie
+"""
+
 
 class test_mail(common.TransactionCase):
 
+    def _mock_smtp_gateway(self, *args, **kwargs):
+        return True
+
+    def _mock_build_email(self, *args, **kwargs):
+        self._build_email_args = args
+        self._build_email_kwargs = kwargs
+        return self.build_email_real(*args, **kwargs)
+
     def setUp(self):
         super(test_mail, self).setUp()
         self.ir_model = self.registry('ir.model')
         self.mail_alias = self.registry('mail.alias')
         self.mail_thread = self.registry('mail.thread')
         self.mail_group = self.registry('mail.group')
+        self.mail_mail = self.registry('mail.mail')
+        self.mail_message = self.registry('mail.message')
+        self.mail_notification = self.registry('mail.notification')
+        self.mail_followers = self.registry('mail.followers')
         self.res_users = self.registry('res.users')
+        self.res_partner = self.registry('res.partner')
+
+        # Install mock SMTP gateway
+        self.build_email_real = self.registry('ir.mail_server').build_email
+        self.registry('ir.mail_server').build_email = self._mock_build_email
+        self.registry('ir.mail_server').send_email = self._mock_smtp_gateway
 
         # groups@.. will cause the creation of new mail groups
-        self.mail_group_model_id = self.ir_model.search(self.cr, self.uid, [('model','=', 'mail.group')])[0]
+        self.mail_group_model_id = self.ir_model.search(self.cr, self.uid, [('model', '=', 'mail.group')])[0]
         self.mail_alias.create(self.cr, self.uid, {'alias_name': 'groups',
                                                    'alias_model_id': self.mail_group_model_id})
-        
-        # tech@... will append new messages to the 'tech' group 
-        self.group_tech_id = self.mail_group.create(self.cr, self.uid, {'name': 'tech'})
+        # create a 'pigs' group that will be used through the various tests
+        self.group_pigs_id = self.mail_group.create(self.cr, self.uid,
+            {'name': 'Pigs', 'description': 'Fans of Pigs, unite !'})
 
-    def test_message_process(self):
+    def test_00_message_process(self):
+        cr, uid = self.cr, self.uid
         # Incoming mail creates a new mail_group "frogs"
-        self.assertEqual(self.mail_group.search(self.cr, self.uid, [('name','=','frogs')]), [])
+        self.assertEqual(self.mail_group.search(cr, uid, [('name', '=', 'frogs')]), [])
         mail_frogs = MAIL_TEMPLATE.format(to='groups@example.com, other@gmail.com', subject='frogs', extra='')
-        self.mail_thread.message_process(self.cr, self.uid, None, mail_frogs)
-        frog_groups = self.mail_group.search(self.cr, self.uid, [('name','=','frogs')])
+        self.mail_thread.message_process(cr, uid, None, mail_frogs)
+        frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'frogs')])
         self.assertTrue(len(frog_groups) == 1)
 
         # Previously-created group can be emailed now - it should have an implicit alias group+frogs@...
-        frog_group = self.mail_group.browse(self.cr, self.uid, frog_groups[0])
+        frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
         group_messages = frog_group.message_ids
         self.assertTrue(len(group_messages) == 1, 'New group should only have the original message')
         mail_frog_news = MAIL_TEMPLATE.format(to='Friendly Frogs <group+frogs@example.com>', subject='news', extra='')
-        self.mail_thread.message_process(self.cr, self.uid, None, mail_frog_news)
+        self.mail_thread.message_process(cr, uid, None, mail_frog_news)
         frog_group.refresh()
         self.assertTrue(len(frog_group.message_ids) == 2, 'Group should contain 2 messages now')
 
         # Even with a wrong destination, a reply should end up in the correct thread
         mail_reply = MAIL_TEMPLATE.format(to='erroneous@example.com>', subject='Re: news',
-                                          extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n'%frog_group.id)
-        self.mail_thread.message_process(self.cr, self.uid, None, mail_reply)
+                                          extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
+        self.mail_thread.message_process(cr, uid, None, mail_reply)
         frog_group.refresh()
         self.assertTrue(len(frog_group.message_ids) == 3, 'Group should contain 3 messages now')
-        
+
         # No model passed and no matching alias must raise
         mail_spam = MAIL_TEMPLATE.format(to='noone@example.com', subject='spam', extra='')
         self.assertRaises(Exception,
                           self.mail_thread.message_process,
-                          self.cr, self.uid, None, mail_spam)
+                          cr, uid, None, mail_spam)
+
+        # plain text content should be wrapped and stored as html
+        test_msg_id = '<deadcafe.1337@smtp.agrolait.com>'
+        mail_text = MAIL_TEMPLATE_PLAINTEXT.format(to='groups@example.com', subject='frogs', extra='', msg_id=test_msg_id)
+        self.mail_thread.message_process(cr, uid, None, mail_text)
+        new_mail = self.mail_message.browse(cr, uid, self.mail_message.search(cr, uid, [('message_id','=',test_msg_id)])[0])
+        self.assertEqual(new_mail.body, '\n<pre>\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n</pre>\n',
+                         'plaintext mail incorrectly parsed')
+
+    def test_10_many2many_reference_field(self):
+        """ Tests designed for the many2many_reference field (follower_ids).
+            We will test to perform writes using the many2many commands 0, 3, 4,
+            5 and 6. """
+        cr, uid = self.cr, self.uid
+        user_admin = self.res_users.browse(cr, uid, uid)
+        group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
+
+        # Create partner Bert Poilu
+        partner_bert_id = self.res_partner.create(cr, uid, {'name': 'Bert Poilu'})
+
+        # Create 'disturbing' values in mail.followers: same res_id, other res_model; same res_model, other res_id
+        group_dummy_id = self.mail_group.create(cr, uid,
+            {'name': 'Dummy group'})
+        self.mail_followers.create(cr, uid,
+            {'res_model': 'mail.thread', 'res_id': self.group_pigs_id, 'partner_id': partner_bert_id})
+        self.mail_followers.create(cr, uid,
+            {'res_model': 'mail.group', 'res_id': group_dummy_id, 'partner_id': partner_bert_id})
+
+        # Pigs just created: should be only Admin as follower
+        follower_ids = set([follower.id for follower in group_pigs.message_follower_ids])
+        self.assertEqual(follower_ids, set([user_admin.partner_id.id]), 'Admin should be the only Pigs fan')
+
+        # Subscribe Bert through a '4' command
+        group_pigs.write({'message_follower_ids': [(4, partner_bert_id)]})
+        group_pigs.refresh()
+        follower_ids = set([follower.id for follower in group_pigs.message_follower_ids])
+        self.assertEqual(follower_ids, set([partner_bert_id, user_admin.partner_id.id]), 'Bert and Admin should be the only Pigs fans')
+
+        # Unsubscribe Bert through a '3' command
+        group_pigs.write({'message_follower_ids': [(3, partner_bert_id)]})
+        group_pigs.refresh()
+        follower_ids = set([follower.id for follower in group_pigs.message_follower_ids])
+        self.assertEqual(follower_ids, set([user_admin.partner_id.id]), 'Admin should be the only Pigs fan')
+
+        # Set followers through a '6' command
+        group_pigs.write({'message_follower_ids': [(6, 0, [partner_bert_id])]})
+        group_pigs.refresh()
+        follower_ids = set([follower.id for follower in group_pigs.message_follower_ids])
+        self.assertEqual(follower_ids, set([partner_bert_id]), 'Bert should be the only Pigs fan')
+
+        # Add a follower created on the fly through a '0' command
+        group_pigs.write({'message_follower_ids': [(0, 0, {'name': 'Patrick Fiori'})]})
+        partner_patrick_id = self.res_partner.search(cr, uid, [('name', '=', 'Patrick Fiori')])[0]
+        group_pigs.refresh()
+        follower_ids = set([follower.id for follower in group_pigs.message_follower_ids])
+        self.assertEqual(follower_ids, set([partner_bert_id, partner_patrick_id]), 'Bert and Patrick should be the only Pigs fans')
+
+        # Finally, unlink through a '5' command
+        group_pigs.write({'message_follower_ids': [(5, 0)]})
+        group_pigs.refresh()
+        follower_ids = set([follower.id for follower in group_pigs.message_follower_ids])
+        self.assertFalse(follower_ids, 'Pigs group should not have fans anymore')
+
+        # Test dummy data has not been altered
+        fol_obj_ids = self.mail_followers.search(cr, uid, [('res_model', '=', 'mail.thread'), ('res_id', '=', self.group_pigs_id)])
+        follower_ids = set([follower.partner_id.id for follower in self.mail_followers.browse(cr, uid, fol_obj_ids)])
+        self.assertEqual(follower_ids, set([partner_bert_id]), 'Bert should be the follower of dummy mail.thread data')
+        fol_obj_ids = self.mail_followers.search(cr, uid, [('res_model', '=', 'mail.group'), ('res_id', '=', group_dummy_id)])
+        follower_ids = set([follower.partner_id.id for follower in self.mail_followers.browse(cr, uid, fol_obj_ids)])
+        self.assertEqual(follower_ids, set([partner_bert_id, user_admin.partner_id.id]), 'Bert and Admin should be the followers of dummy mail.group data')
+
+    def test_11_message_followers(self):
+        """ Tests designed for the subscriber API. """
+        cr, uid = self.cr, self.uid
+        user_admin = self.res_users.browse(cr, uid, uid)
+        group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
+
+        # Create user Raoul
+        user_raoul_id = self.res_users.create(cr, uid, {'name': 'Raoul Grosbedon', 'login': 'raoul'})
+        user_raoul = self.res_users.browse(cr, uid, user_raoul_id)
+
+        # Subscribe Raoul three times (niak niak) through message_subscribe_users
+        group_pigs.message_subscribe_users([user_raoul_id, user_raoul_id])
+        group_pigs.message_subscribe_users([user_raoul_id])
+        group_pigs.refresh()
+        follower_ids = [follower.id for follower in group_pigs.message_follower_ids]
+        self.assertEqual(len(follower_ids), 2, 'There should be 2 Pigs fans')
+        self.assertEqual(set(follower_ids), set([user_raoul.partner_id.id, user_admin.partner_id.id]), 'Admin and Raoul should be the only 2 Pigs fans')
+
+        # Unsubscribe Raoul twice through message_unsubscribe_users
+        group_pigs.message_unsubscribe_users([user_raoul_id, user_raoul_id])
+        group_pigs.refresh()
+        follower_ids = [follower.id for follower in group_pigs.message_follower_ids]
+        self.assertEqual(follower_ids, [user_admin.partner_id.id], 'Admin must be the only Pigs fan')
+
+    def test_20_message_post(self):
+        """ Tests designed for message_post. """
+        cr, uid = self.cr, self.uid
+        self.res_users.write(cr, uid, [uid], {'signature': 'Admin', 'email': 'a@a'})
+        user_admin = self.res_users.browse(cr, uid, uid)
+        group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
+
+        # 0 - Admin
+        p_a_id = user_admin.partner_id.id
+        # 1 - Bert Tartopoils, with email, should receive emails for comments and emails
+        p_b_id = self.res_partner.create(cr, uid, {'name': 'Bert Tartopoils', 'email': 'b@b'})
+        # 2 - Carine Poilvache, with email, should never receive emails
+        p_c_id = self.res_partner.create(cr, uid, {'name': 'Carine Poilvache', 'email': 'c@c', 'notification_email_send': 'email'})
+        # 3 - Dédé Grosbedon, without email, to test email verification; should receive emails for every message
+        p_d_id = self.res_partner.create(cr, uid, {'name': 'Dédé Grosbedon', 'notification_email_send': 'all'})
+
+        # Subscribe #1, #2
+        group_pigs.message_subscribe([p_b_id, p_c_id])
+
+        # Mail data
+        _subject = 'Pigs'
+        _mail_subject = '%s posted on %s' % (user_admin.name, group_pigs.name)
+        _body1 = 'Pigs rules'
+        _mail_body1 = 'Pigs rules\n<pre>Admin</pre>\n'
+        _mail_bodyalt1 = 'Pigs rules\nAdmin'
+        _body2 = '<html>Pigs rules</html>'
+        _mail_body2 = html_sanitize('<html>Pigs rules\n<pre>Admin</pre>\n</html>')
+        _mail_bodyalt2 = 'Pigs rules\nAdmin'
+        _attachments = [('First', 'My first attachment'), ('Second', 'My second attachment')]
+
+        # CASE1: post comment, body and subject specified
+        msg_id = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body1, subject=_subject, type='comment')
+        message = self.mail_message.browse(cr, uid, msg_id)
+        sent_email = self._build_email_kwargs
+        # Test: notifications have been deleted
+        self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg_id)]), 'mail.mail notifications should have been auto-deleted!')
+        # Test: mail_message: subject is _subject, body is _body1 (no formatting done)
+        self.assertEqual(message.subject, _subject, 'mail.message subject incorrect')
+        self.assertEqual(message.body, _body1, 'mail.message body incorrect')
+        # Test: sent_email: email send by server: correct subject, body; body_alternative
+        self.assertEqual(sent_email['subject'], _subject, 'sent_email subject incorrect')
+        self.assertEqual(sent_email['body'], _mail_body1, 'sent_email body incorrect')
+        self.assertEqual(sent_email['body_alternative'], _mail_bodyalt1, 'sent_email body_alternative is incorrect')
+        # Test: mail_message: partner_ids = group followers
+        message_pids = set([partner.id for partner in message.partner_ids])
+        test_pids = set([p_a_id, p_b_id, p_c_id])
+        self.assertEqual(test_pids, message_pids, 'mail.message partners incorrect')
+        # Test: notification linked to this message = group followers = partner_ids
+        notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.id)])
+        notif_pids = set([notif.partner_id.id for notif in self.mail_notification.browse(cr, uid, notif_ids)])
+        self.assertEqual(notif_pids, test_pids, 'mail.message notification partners incorrect')
+        # Test: sent_email: email_to should contain b@b, not c@c (pref email), not a@a (writer)
+        self.assertEqual(sent_email['email_to'], ['b@b'], 'sent_email email_to is incorrect')
+
+        # CASE2: post an email with attachments, parent_id, partner_ids
+        # TESTS: automatic subject, signature in body_html, attachments propagation
+        msg_id2 = self.mail_group.message_post(cr, uid, self.group_pigs_id, body=_body2, type='email',
+            partner_ids=[(6, 0, [p_d_id])], parent_id=msg_id, attachments=_attachments)
+        message = self.mail_message.browse(cr, uid, msg_id2)
+        sent_email = self._build_email_kwargs
+        self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg_id2)]), 'mail.mail notifications should have been auto-deleted!')
+
+        # Test: mail_message: subject is False, body is _body2 (no formatting done), parent_id is msg_id
+        self.assertEqual(message.subject, False, 'mail.message subject incorrect')
+        self.assertEqual(message.body, html_sanitize(_body2), 'mail.message body incorrect')
+        self.assertEqual(message.parent_id.id, msg_id, 'mail.message parent_id incorrect')
+        # Test: sent_email: email send by server: correct subject, body, body_alternative
+        self.assertEqual(sent_email['subject'], _mail_subject, 'sent_email subject incorrect')
+        self.assertEqual(sent_email['body'], _mail_body2, 'sent_email body incorrect')
+        self.assertEqual(sent_email['body_alternative'], _mail_bodyalt2, 'sent_email body_alternative incorrect')
+        # Test: mail_message: partner_ids = group followers
+        message_pids = set([partner.id for partner in message.partner_ids])
+        test_pids = set([p_a_id, p_b_id, p_c_id, p_d_id])
+        self.assertEqual(message_pids, test_pids, 'mail.message partners incorrect')
+        # Test: notifications linked to this message = group followers = partner_ids
+        notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.id)])
+        notif_pids = set([notif.partner_id.id for notif in self.mail_notification.browse(cr, uid, notif_ids)])
+        self.assertEqual(notif_pids, test_pids, 'mail.message notification partners incorrect')
+        # Test: sent_email: email_to should contain b@b, c@c, not a@a (writer)
+        self.assertEqual(set(sent_email['email_to']), set(['b@b', 'c@c']), 'sent_email email_to incorrect')
+        # Test: attachments
+        for attach in message.attachment_ids:
+            self.assertEqual(attach.res_model, 'mail.group', 'mail.message attachment res_model incorrect')
+            self.assertEqual(attach.res_id, self.group_pigs_id, 'mail.message attachment res_id incorrect')
+            self.assertIn((attach.name, attach.datas.decode('base64')), _attachments,
+                'mail.message attachment name / data incorrect')
+
+    def test_21_message_compose_wizard(self):
+        """ Tests designed for the mail.compose.message wizard. """
+        cr, uid = self.cr, self.uid
+        mail_compose = self.registry('mail.compose.message')
+        self.res_users.write(cr, uid, [uid], {'signature': 'Admin', 'email': 'a@a'})
+        user_admin = self.res_users.browse(cr, uid, uid)
+        group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
+        group_bird_id = self.mail_group.create(cr, uid, {'name': 'Bird', 'description': 'Bird resistance'})
+        group_bird = self.mail_group.browse(cr, uid, group_bird_id)
+
+        # Mail data
+        _subject = 'Pigs'
+        _body_text = 'Pigs rules'
+        _msg_reply = 'Re: Pigs'
+        _msg_body = '<pre>Pigs rules</pre>'
+        _attachments = [
+            {'name': 'First', 'datas_fname': 'first.txt', 'datas': 'My first attachment'.encode('base64')},
+            {'name': 'Second', 'datas_fname': 'second.txt', 'datas': 'My second attachment'.encode('base64')}
+            ]
+        _attachments_test = [('first.txt', 'My first attachment'), ('second.txt', 'My second attachment')]
+
+        # Create partners
+        # 0 - Admin
+        p_a_id = user_admin.partner_id.id
+        # 1 - Bert Tartopoils, with email, should receive emails for comments and emails
+        p_b_id = self.res_partner.create(cr, uid, {'name': 'Bert Tartopoils', 'email': 'b@b'})
+        # 2 - Carine Poilvache, with email, should never receive emails
+        p_c_id = self.res_partner.create(cr, uid, {'name': 'Carine Poilvache', 'email': 'c@c', 'notification_email_send': 'email'})
+        # 3 - Dédé Grosbedon, without email, to test email verification; should receive emails for every message
+        p_d_id = self.res_partner.create(cr, uid, {'name': 'Dédé Grosbedon', 'notification_email_send': 'all'})
+
+        # Subscribe #1
+        group_pigs.message_subscribe([p_b_id])
+
+        # ----------------------------------------
+        # CASE1: comment on group_pigs
+        # ----------------------------------------
+
+        # 1. Comment group_pigs with body_text and subject
+        compose_id = mail_compose.create(cr, uid,
+            {'subject': _subject, 'body_text': _body_text, 'partner_ids': [(4, p_c_id), (4, p_d_id)]},
+            {'default_composition_mode': 'comment', 'default_model': 'mail.group', 'default_res_id': self.group_pigs_id})
+        compose = mail_compose.browse(cr, uid, compose_id)
+        # Test: mail.compose.message: composition_mode, model, res_id
+        self.assertEqual(compose.composition_mode,  'comment', 'mail.compose.message incorrect composition_mode')
+        self.assertEqual(compose.model,  'mail.group', 'mail.compose.message incorrect model')
+        self.assertEqual(compose.res_id, self.group_pigs_id, 'mail.compose.message incorrect res_id')
+
+        # 2. Post the comment, get created message
+        mail_compose.send_mail(cr, uid, [compose_id])
+        group_pigs.refresh()
+        message = group_pigs.message_ids[0]
+        # Test: mail.message: subject, body inside pre
+        self.assertEqual(message.subject,  False, 'mail.message incorrect subject')
+        self.assertEqual(message.body, _msg_body, 'mail.message incorrect body')
+        # Test: mail.message: partner_ids = entries in mail.notification: group_pigs fans (a, b) + mail.compose.message partner_ids (c, d)
+        msg_pids = [partner.id for partner in message.partner_ids]
+        test_pids = [p_a_id, p_b_id, p_c_id, p_d_id]
+        notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.id)])
+        self.assertEqual(len(notif_ids), 4, 'mail.message: too much notifications created')
+        self.assertEqual(set(msg_pids), set(test_pids), 'mail.message partner_ids incorrect')
+
+        # ----------------------------------------
+        # CASE2: reply to last comment with attachments
+        # ----------------------------------------
+
+        # 1. Update last comment subject, reply with attachments
+        message.write({'subject': _subject})
+        compose_id = mail_compose.create(cr, uid,
+            {'attachment_ids': [(0, 0, _attachments[0]), (0, 0, _attachments[1])]},
+            {'default_composition_mode': 'reply', 'default_model': 'mail.thread', 'default_res_id': self.group_pigs_id, 'default_parent_id': message.id})
+        compose = mail_compose.browse(cr, uid, compose_id)
+        # Test: model, res_id, parent_id, content_subtype
+        self.assertEqual(compose.model,  'mail.group', 'mail.compose.message incorrect model')
+        self.assertEqual(compose.res_id, self.group_pigs_id, 'mail.compose.message incorrect res_id')
+        self.assertEqual(compose.parent_id.id, message.id, 'mail.compose.message incorrect parent_id')
+        self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message incorrect content_subtype')
+
+        # 2. Post the comment, get created message
+        mail_compose.send_mail(cr, uid, [compose_id])
+        group_pigs.refresh()
+        message = group_pigs.message_ids[0]
+        # Test: mail.message: subject as Re:.., body in html
+        self.assertEqual(message.subject, _msg_reply, 'mail.message incorrect subject')
+        self.assertIn('Administrator wrote:<blockquote><pre>Pigs rules</pre></blockquote></div>', message.body, 'mail.message body is incorrect')
+        # Test: mail.message: attachments
+        for attach in message.attachment_ids:
+            self.assertEqual(attach.res_model, 'mail.group', 'mail.message attachment res_model incorrect')
+            self.assertEqual(attach.res_id, self.group_pigs_id, 'mail.message attachment res_id incorrect')
+            self.assertIn((attach.name, attach.datas.decode('base64')), _attachments_test,
+                'mail.message attachment name / data incorrect')
+
+        # ----------------------------------------
+        # CASE3: mass_mail on Pigs and Bird
+        # ----------------------------------------
+
+        # 1. mass_mail on pigs and bird
+        compose_id = mail_compose.create(cr, uid,
+            {'subject': _subject, 'body': '${object.description}'},
+            {'default_composition_mode': 'mass_mail', 'default_model': 'mail.group', 'default_res_id': -1,
+                'active_ids': [self.group_pigs_id, group_bird_id]})
+        compose = mail_compose.browse(cr, uid, compose_id)
+        # Test: content_subtype is html
+        self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message content_subtype incorrect')
+
+        # 2. Post the comment, get created message for each group
+        mail_compose.send_mail(cr, uid, [compose_id],
+            context={'default_res_id': -1, 'active_ids': [self.group_pigs_id, group_bird_id]})
+        group_pigs.refresh()
+        group_bird.refresh()
+        message1 = group_pigs.message_ids[0]
+        message2 = group_bird.message_ids[0]
+        # Test: Pigs and Bird did receive their message
+        test_msg_ids = self.mail_message.search(cr, uid, [], limit=2)
+        self.assertIn(message1.id, test_msg_ids, 'Pigs did not receive its mass mailing message')
+        self.assertIn(message2.id, test_msg_ids, 'Bird did not receive its mass mailing message')
+        # Test: mail.message: subject, body
+        self.assertEqual(message1.subject, _subject, 'mail.message subject incorrect')
+        self.assertEqual(message1.body, group_pigs.description, 'mail.message body incorrect')
+        self.assertEqual(message2.subject, _subject, 'mail.message subject incorrect')
+        self.assertEqual(message2.body, group_bird.description, 'mail.message body incorrect')
+
+    def test_30_message_read(self):
+        """ Tests designed for message_read. """
+        # TDE NOTE: this test is not finished, as the message_read method is not fully specified.
+        # It will be updated as soon as we have fixed specs !
+        cr, uid = self.cr, self.uid
+        group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
+        def _compare_structures(struct1, struct2, n=0):
+            # print '%scompare structure' % ('\t' * n)
+            self.assertEqual(len(struct1), len(struct2), 'message_read structure number of childs incorrect')
+            for x in range(len(struct1)):
+                # print '%s' % ('\t' * n), struct1[x]['id'], struct2[x]['id'], struct1[x].get('subject') or ''
+                self.assertEqual(struct1[x]['id'], struct2[x]['id'], 'message_read failure %s' % struct1[x].get('subject'))
+                _compare_structures(struct1[x]['child_ids'], struct2[x]['child_ids'], n + 1)
+            # print '%send compare' % ('\t' * n)
+
+        # ----------------------------------------
+        # CASE1: Flattening test
+        # ----------------------------------------
+
+        # Create dummy message structure
+        import copy
+        tree = [{'id': 2, 'child_ids': [
+                    {'id': 6, 'child_ids': [
+                        {'id': 8, 'child_ids': []},
+                        ]},
+                    ]},
+                {'id': 1, 'child_ids':[
+                    {'id': 7, 'child_ids': [
+                        {'id': 9, 'child_ids': []},
+                        ]},
+                    {'id': 4, 'child_ids': [
+                        {'id': 10, 'child_ids': []},
+                        {'id': 5, 'child_ids': []},
+                        ]},
+                    {'id': 3, 'child_ids': []},
+                    ]},
+                ]
+        # Test: completely flat
+        new_tree = self.mail_message.message_read_tree_flatten(cr, uid, copy.deepcopy(tree), 0, 0)
+        self.assertEqual(len(new_tree), 10, 'message_read_tree_flatten wrong in flat')
+        # Test: 1 thread level
+        tree_test = [{'id': 2, 'child_ids': [
+                        {'id': 8, 'child_ids': []}, {'id': 6, 'child_ids': []},
+                    ]},
+                    {'id': 1, 'child_ids': [
+                        {'id': 10, 'child_ids': []}, {'id': 9, 'child_ids': []},
+                        {'id': 7, 'child_ids': []}, {'id': 5, 'child_ids': []},
+                        {'id': 4, 'child_ids': []}, {'id': 3, 'child_ids': []},
+                    ]},
+                    ]
+        new_tree = self.mail_message.message_read_tree_flatten(cr, uid, copy.deepcopy(tree), 0, 1)
+        _compare_structures(new_tree, tree_test)
+        # Test: 2 thread levels
+        new_tree = self.mail_message.message_read_tree_flatten(cr, uid, copy.deepcopy(tree), 0, 2)
+        _compare_structures(new_tree, tree)
+
+        # ----------------------------------------
+        # CASE2: message_read test
+        # ----------------------------------------
+
+        # 1. Add a few messages to pigs group
+        msgid1 = group_pigs.message_post(body='1', subject='1', parent_id=False)
+        msgid2 = group_pigs.message_post(body='2', subject='1-1', parent_id=msgid1)
+        msgid3 = group_pigs.message_post(body='3', subject='1-2', parent_id=msgid1)
+        msgid4 = group_pigs.message_post(body='4', subject='2', parent_id=False)
+        msgid5 = group_pigs.message_post(body='5', subject='1-1-1', parent_id=msgid2)
+        msgid6 = group_pigs.message_post(body='6', subject='2-1', parent_id=msgid4)
+
+        # Test: read all messages flat
+        tree_test = [{'id': msgid6, 'child_ids': []}, {'id': msgid5, 'child_ids': []},
+                        {'id': msgid4, 'child_ids': []}, {'id': msgid3, 'child_ids': []},
+                        {'id': msgid2, 'child_ids': []}, {'id': msgid1, 'child_ids': []}]
+        tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], thread_level=0, limit=10)
+        _compare_structures(tree, tree_test)
+        # Test: read with 1 level of thread
+        tree_test = [{'id': msgid4, 'child_ids': [{'id': msgid6, 'child_ids': []}, ]},
+                    {'id': msgid1, 'child_ids': [
+                        {'id': msgid5, 'child_ids': []}, {'id': msgid3, 'child_ids': []},
+                        {'id': msgid2, 'child_ids': []},
+                    ]},
+                    ]
+        tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], thread_level=1, limit=10)
+        _compare_structures(tree, tree_test)
+        # Test: read with 2 levels of thread
+        tree_test = [{'id': msgid4, 'child_ids': [{'id': msgid6, 'child_ids': []}, ]},
+                    {'id': msgid1, 'child_ids': [
+                        {'id': msgid3, 'child_ids': []},
+                        {'id': msgid2, 'child_ids': [{'id': msgid5, 'child_ids': []}, ]},
+                    ]},
+                    ]
+        tree = self.mail_message.message_read(cr, uid, ids=False, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], thread_level=2, limit=10)
+        _compare_structures(tree, tree_test)
+
+        # 2. Test expandables
+        # TDE FIXME: add those tests when expandables are specified and implemented
+
+    def test_40_needaction(self):
+        """ Tests for mail.message needaction. """
+        cr, uid = self.cr, self.uid
+        group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
+        user_admin = self.res_users.browse(cr, uid, uid)
+
+        # Demo values: check unread notification = needaction on mail.message
+        notif_ids = self.mail_notification.search(cr, uid, [
+            ('partner_id', '=', user_admin.partner_id.id),
+            ('read', '=', False)
+            ])
+        na_count = self.mail_message._needaction_count(cr, uid, domain=[])
+        self.assertEqual(len(notif_ids), na_count, 'unread notifications count does not match needaction count')
+
+        # Post 4 message on group_pigs
+        for dummy in range(4):
+            group_pigs.message_post(body='My Body')
+
+        # Check there are 4 new needaction on mail.message
+        notif_ids = self.mail_notification.search(cr, uid, [
+            ('partner_id', '=', user_admin.partner_id.id),
+            ('read', '=', False)
+            ])
+        na_count = self.mail_message._needaction_count(cr, uid, domain=[])
+        self.assertEqual(len(notif_ids), na_count, 'unread notifications count does not match needaction count')
+
+        # Check there are 4 needaction on mail.message with particular domain
+        na_count = self.mail_message._needaction_count(cr, uid, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)])
+        self.assertEqual(na_count, 4, 'posted message count does not match needaction count')
+
+    def test_50_thread_parent_resolution(self):
+        """Verify parent/child relationships are correctly established when processing incoming mails"""
+        cr, uid = self.cr, self.uid
+        group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
+        msg1 = group_pigs.message_post(body='My Body', subject='1')
+        msg2 = group_pigs.message_post(body='My Body', subject='2')
+        msg1, msg2 = self.mail_message.browse(cr, uid, [msg1, msg2])
+        self.assertTrue(msg1.message_id, "New message should have a proper message_id")
+
+        # Reply to msg1, make sure the reply is properly attached using the various reply identification mechanisms
+        # 1. In-Reply-To header
+        reply_msg = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com', subject='Re: 1',
+                                         extra='In-Reply-To: %s' % msg1.message_id)
+        self.mail_thread.message_process(cr, uid, None, reply_msg)
+        # 2. References header
+        reply_msg2 = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com', subject='Re: Re: 1',
+                                         extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % msg1.message_id)
+        self.mail_thread.message_process(cr, uid, None, reply_msg2)
+        # 3. Subject contains [<ID>] + model passed to message+process -> only attached to group, not to mail
+        reply_msg3 = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com',
+                                          extra='', subject='Re: [%s] 1' % self.group_pigs_id)
+        self.mail_thread.message_process(cr, uid, 'mail.group', reply_msg3)
+        group_pigs.refresh()
+        msg1.refresh()
+        self.assertEqual(5, len(group_pigs.message_ids), 'group should contain 5 messages')
+        self.assertEqual(2, len(msg1.child_ids), 'msg1 should have 2 children now')
index fe87b75..3153ccc 100644 (file)
 #
 ##############################################################################
 
-import ast
+import base64
 import re
-
 import tools
+
 from osv import osv
 from osv import fields
 from tools.safe_eval import safe_eval as eval
 from tools.translate import _
 
-from ..mail_message import to_email
-
 # main mako-like expression pattern
 EXPRESSION_PATTERN = re.compile('(\$\{.+?\})')
 
 class mail_compose_message(osv.TransientModel):
-    """Generic Email composition wizard. This wizard is meant to be inherited
-       at model and view level to provide specific wizard features.
-
-       The behavior of the wizard can be modified through the use of context
-       parameters, among which are:
-
-         * mail.compose.message.mode: if set to 'reply', the wizard is in 
-            reply to a previous message mode and pre-populated with the original
-            quote. If set to 'comment', it means you are writing a new message to
-            be attached to a document. If set to 'mass_mail', the wizard is in
-            mass mailing where the mail details can contain template placeholders
-            that will be merged with actual data before being sent to each
-            recipient.
-         * active_model: model name of the document to which the mail being
-                        composed is related
-         * active_id: id of the document to which the mail being composed is
-                      related, or id of the message to which user is replying,
-                      in case ``mail.compose.message.mode == 'reply'``
-         * active_ids: ids of the documents to which the mail being composed is
-                      related, in case ``mail.compose.message.mode == 'mass_mail'``.
+    """ Generic message composition wizard. You may inherit from this wizard
+        at model and view levels to provide specific features.
+
+        The behavior of the wizard depends on the composition_mode field:
+        - 'reply': reply to a previous message. The wizard is pre-populated
+            via ``get_message_data``.
+        - 'comment': new post on a record. The wizard is pre-populated via
+            ``get_record_data``
+        - 'mass_mail': wizard in mass mailing mode where the mail details can
+            contain template placeholders that will be merged with actual data
+            before being sent to each recipient.
     """
     _name = 'mail.compose.message'
-    _inherit = 'mail.message.common'
+    _inherit = 'mail.message'
     _description = 'Email composition wizard'
+    _log_access = True
 
     def default_get(self, cr, uid, fields, context=None):
-        """ Overridden to provide specific defaults depending on the context
-            parameters.
-
-            Composition mode
-            - comment: default mode; active_model, active_id = model and ID of a
-            document we are commenting,
-            - reply: active_id = ID of a mail.message to which we are replying.
-            From this message we can find the related model and res_id,
-            - mass_mailing mode: active_model, active_id  = model and ID of a
-            document we are commenting,
-
-           :param dict context: several context values will modify the behavior
-                                of the wizard, cfr. the class description.
+        """ Handle composition mode. Some details about context keys:
+            - comment: default mode, model and ID of a record the user comments
+                - default_model or active_model
+                - default_res_id or active_id
+            - reply: active_id of a message the user replies to
+                - default_parent_id or message_id or active_id: ID of the
+                    mail.message we reply to
+                - message.res_model or default_model
+                - message.res_id or default_res_id
+            - mass_mail: model and IDs of records the user mass-mails
+                - active_ids: record IDs
+                - default_model or active_model
         """
         if context is None:
             context = {}
-        compose_mode = context.get('mail.compose.message.mode', 'comment')
-        active_model = context.get('active_model')
-        active_id = context.get('active_id')
         result = super(mail_compose_message, self).default_get(cr, uid, fields, context=context)
 
+        # get some important values from context
+        composition_mode = context.get('default_composition_mode', context.get('mail.compose.message.mode'))
+        model = context.get('default_model', context.get('active_model'))
+        res_id = context.get('default_res_id', context.get('active_id'))
+        message_id = context.get('default_parent_id', context.get('message_id', context.get('active_id')))
+        active_ids = context.get('active_ids')
+
         # get default values according to the composition mode
-        vals = {}
-        if compose_mode in ['reply']:
-            vals = self.get_message_data(cr, uid, int(context['active_id']), context=context)
-        elif compose_mode in ['comment', 'mass_mail'] and active_model and active_id:
-            vals = self.get_value(cr, uid, active_model, active_id, context)
+        if composition_mode == 'reply':
+            vals = self.get_message_data(cr, uid, message_id, context=context)
+        elif composition_mode == 'comment' and model and res_id:
+            vals = self.get_record_data(cr, uid, model, res_id, context=context)
+        elif composition_mode == 'mass_mail' and model and active_ids:
+            vals = {'model': model, 'res_id': res_id, 'content_subtype': 'html'}
+        else:
+            vals = {'model': model, 'res_id': res_id}
+        if composition_mode:
+            vals['composition_mode'] = composition_mode
+
         for field in vals:
             if field in fields:
                 result[field] = vals[field]
-
-        # link to model and record if not done yet
-        if not result.get('model') and active_model:
-            result['model'] = active_model
-        if not result.get('res_id') and active_id:
-            result['res_id'] = active_id
-
-        # Try to provide default email_from if not specified yet
-        if not result.get('email_from'):
-            current_user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
-            result['email_from'] = current_user.email or False
         return result
 
+    def _get_composition_mode_selection(self, cr, uid, context=None):
+        return [('comment', 'Comment a document'), ('reply', 'Reply to a message'), ('mass_mail', 'Mass mailing')]
+
     _columns = {
-        'dest_partner_ids': fields.many2many('res.partner',
-            'email_message_send_partner_rel',
-            'wizard_id', 'partner_id', 'Destination partners',
-            help="When sending emails through the social network composition wizard"\
-                 "you may choose to send a copy of the mail to partners."),
-        'attachment_ids': fields.many2many('ir.attachment','email_message_send_attachment_rel', 'wizard_id', 'attachment_id', 'Attachments'),
-        'auto_delete': fields.boolean('Auto Delete', help="Permanently delete emails after sending"),
+        'composition_mode': fields.selection(
+            lambda s, *a, **k: s._get_composition_mode_selection(*a, **k),
+            string='Composition mode'),
+        'partner_ids': fields.many2many('res.partner',
+            'mail_compose_message_res_partner_rel',
+            'wizard_id', 'partner_id', 'Additional contacts'),
+        'attachment_ids': fields.many2many('ir.attachment',
+            'mail_compose_message_ir_attachments_rel',
+            'wizard_id', 'attachment_id', 'Attachments'),
         'filter_id': fields.many2one('ir.filters', 'Filters'),
+        'body_text': fields.text('Plain-text Contents'),
+        'content_subtype': fields.char('Message content subtype', size=32, readonly=1,
+            help="Type of message, usually 'html' or 'plain', used to select "\
+                  "plain-text or rich-text contents accordingly"),
+    }
+
+    _defaults = {
+        'composition_mode': 'comment',
+        'content_subtype': lambda self, cr, uid, ctx={}: 'plain',
+        'body_text': lambda self, cr, uid, ctx={}: False,
+        'body': lambda self, cr, uid, ctx={}: '',
+        'subject': lambda self, cr, uid, ctx={}: False,
     }
 
-    def get_value(self, cr, uid, model, res_id, context=None):
+    def notify(self, cr, uid, newid, context=None):
+        """ Override specific notify method of mail.message, because we do
+            not want that feature in the wizard. """
+        return
+
+    def get_record_data(self, cr, uid, model, res_id, context=None):
         """ Returns a defaults-like dict with initial values for the composition
             wizard when sending an email related to the document record
             identified by ``model`` and ``res_id``.
 
-            The default implementation returns an empty dictionary, and is meant
-            to be overridden by subclasses.
-
             :param str model: model name of the document record this mail is
                 related to.
-            :param int res_id: id of the document record this mail is related to.
-            :param dict context: several context values will modify the behavior
-                of the wizard, cfr. the class description.
+            :param int res_id: id of the document record this mail is related to
         """
-        result = {}
-        user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
-        result.update({
-            'model': model,
-            'res_id': res_id,
-            'email_from': user.email or tools.config.get('email_from', False),
-            'body_html': False,
-            'body_text': False,
-            'subject': False,
-            'dest_partner_ids': [],
-        })
-        return result
-
-    def onchange_email_mode(self, cr, uid, ids, value, model, res_id, context=None):
-        """ email_mode (values: True or False). This onchange on the email mode
-            allows to have some specific behavior when going in email mode, or
-            when going out of email mode.
-            Basically, dest_partner_ids is reset when going out of email
-            mode.
-            This method can be overridden for models that want to have their
-            specific behavior.
-            Note that currently, this onchange is used in mail.js and called
-            manually on the form instantiated in the Chatter.
-        """
-        if not value:
-            return {'value': {'dest_partner_ids': []}}
-        return {'value': {}}
-
-    def onchange_formatting(self, cr, uid, ids, value, model, res_id, context=None):
-        """ onchange_formatting (values: True or False). This onchange on the
-            formatting allows to have some specific behavior when going in 
-            formatting mode, or when going out of formatting.
-            Basically, subject is reset when going out of formatting mode.
-            This method can be overridden for models that want to have their
-            specific behavior.
-            Note that currently, this onchange is used in mail.js and called
-            manually on the form instantiated in the Chatter.
-        """
-        if not value:
-            return {'value': {'subject': False}}
-        return {'value': {}}
+        return {'model': model, 'res_id': res_id}
 
     def get_message_data(self, cr, uid, message_id, context=None):
         """ Returns a defaults-like dict with initial values for the composition
             wizard when replying to the given message (e.g. including the quote
-            of the initial message, and the correct recipient). It should not be
-            called unless ``context['mail.compose.message.mode'] == 'reply'``.
+            of the initial message, and the correct recipients).
 
             :param int message_id: id of the mail.message to which the user
                 is replying.
-            :param dict context: several context values will modify the behavior
-                of the wizard, cfr. the class description.
         """
+        if not message_id:
+            return {}
         if context is None:
             context = {}
-        result = {}
-        if not message_id:
-            return result
+        message_data = self.pool.get('mail.message').browse(cr, uid, message_id, context=context)
 
-        current_user = self.pool.get('res.users').browse(cr, uid, uid, context)
-        message_data = self.pool.get('mail.message').browse(cr, uid, message_id, context)
-        # Form the subject
-        re_prefix = _("Re:")
+        # create subject
+        re_prefix = _('Re:')
         reply_subject = tools.ustr(message_data.subject or '')
         if not (reply_subject.startswith('Re:') or reply_subject.startswith(re_prefix)):
             reply_subject = "%s %s" % (re_prefix, reply_subject)
-        # Form the bodies (text and html). We use the plain text version of the
-        # original mail, by default, as it is easier to quote than the HTML
-        # version. TODO: make it possible to switch to HTML on the fly
-        sent_date = _('On %(date)s, ') % {'date': message_data.date} if message_data.date else ''
-        sender = _('%(sender_name)s wrote:') % {'sender_name': tools.ustr(message_data.email_from or _('You'))}
-        body_text = message_data.body_text or ''
-        body_html = message_data.body_html or ''
-        quoted_body_text = '> %s' % tools.ustr(body_text.replace('\n', "\n> ") or '')
-        quoted_body_html = '<blockquote>%s</blockquote>' % (tools.ustr(body_html)),
-        reply_body_text = '\n%s%s\n%s\n%s' % (sent_date, sender, quoted_body_text, current_user.signature)
-        reply_body_html = '<br /><br />%s%s<br />%s<br />%s' % (sent_date, sender, quoted_body_html, current_user.signature)
-        # form dest_partner_ids
-        dest_partner_ids = [partner.id for partner in message_data.partner_ids]
-        # Update header and references
-        reply_headers = {}
-        reply_references = message_data.references and tools.ustr(message_data.references) or False
-        reply_message_id = message_data.message_id or False
-        if reply_message_id:
-            reply_references = (reply_references or '') + " " + message_data.message_id
-            reply_headers['In-Reply-To'] = message_data.message_id
+        # create the reply in the body
+        reply_body = _('<div>On %(date)s, %(sender_name)s wrote:<blockquote>%(body)s</blockquote></div>') % {
+            'date': message_data.date if message_data.date else '',
+            'sender_name': message_data.author_id.name,
+            'body': message_data.body,
+            }
+        # get partner_ids from original message
+        partner_ids = [partner.id for partner in message_data.partner_ids] if message_data.partner_ids else []
+
         # update the result
-        result.update({
-            'body_text': reply_body_text,
-            'body_html': reply_body_html,
+        result = {
+            'model': message_data.model,
+            'res_id': message_data.res_id,
+            'parent_id': message_data.id,
+            'body': reply_body,
             'subject': reply_subject,
-            'attachment_ids': [],
-            'dest_partner_ids': dest_partner_ids,
-            'model': message_data.model or False,
-            'res_id': message_data.res_id or False,
-            'email_from': current_user.email or message_data.email_to or False,
-            'email_to': message_data.reply_to or message_data.email_from or False,
-            'email_cc': message_data.email_cc or False,
-            'user_id': uid,
-            # pass msg-id and references of mail we're replying to, to construct the
-            # new ones later when sending
-            'message_id': reply_message_id,
-            'references': reply_references,
-            'headers': reply_headers,
-        })
+            'partner_ids': partner_ids,
+            'content_subtype': 'html',
+        }
         return result
 
+    def toggle_content_subtype(self, cr, uid, ids, context=None):
+        """ hit toggle formatting mode button: calls onchange_formatting to
+            emulate an on_change, then writes the value to update the form. """
+        for record in self.browse(cr, uid, ids, context=context):
+            content_st_new_value = 'plain' if record.content_subtype == 'html' else 'html'
+            onchange_res = self.onchange_content_subtype(cr, uid, ids, content_st_new_value, record.model, record.res_id, context=context)
+            self.write(cr, uid, [record.id], onchange_res['value'], context=context)
+        return True
+
+    def onchange_content_subtype(self, cr, uid, ids, value, model, res_id, context=None):
+        """ onchange_content_subtype (values: 'plain' or 'html'). This onchange
+            on the subtype allows to have some specific behavior when switching
+            between text or html mode.
+            This method can be overridden for models that want to have their
+            specific behavior. """
+        return {'value': {'content_subtype': value}}
+
+    def _verify_partner_email(self, cr, uid, partner_ids, context=None):
+        """ Verify that selected partner_ids have an email_address defined.
+            Otherwise throw a warning. """
+        partner_wo_email_lst = []
+        for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids, context=context):
+            if not partner.email:
+                partner_wo_email_lst.append(partner)
+        if not partner_wo_email_lst:
+            return {}
+        warning_msg = _('The following partners chosen as recipients for the email have no email address linked :')
+        for partner in partner_wo_email_lst:
+            warning_msg += '\n- %s' % (partner.name)
+        return {'warning': {
+                    'title': _('Partners email addresses not found'),
+                    'message': warning_msg,
+                    }
+                }
+
+    def onchange_partner_ids(self, cr, uid, ids, value, context=None):
+        """ onchange_partner_ids (value format: [[6, 0, [3, 4]]]). The
+            basic purpose of this method is to check that destination partners
+            effectively have email addresses. Otherwise a warning is thrown.
+        """
+        res = {'value': {}}
+        if not value or not value[0] or not value[0][0] == 6:
+            return
+        res.update(self._verify_partner_email(cr, uid, value[0][2], context=context))
+        return res
+
+    def unlink(self, cr, uid, ids, context=None):
+        # Cascade delete all attachments, as they are owned by the composition wizard
+        for wizard in self.read(cr, uid, ids, ['attachment_ids'], context=context):
+            self.pool.get('ir.attachment').unlink(cr, uid, wizard['attachment_ids'], context=context)
+        return super(mail_compose_message, self).unlink(cr, uid, ids, context=context)
+
+    def dummy(self, cr, uid, ids, context=None):
+        """ TDE: defined to have buttons that do basically nothing. It is
+            currently impossible to have buttons that do nothing special
+            in views (if type not specified, considered as 'object'). """
+        return True
+
+    #------------------------------------------------------
+    # Wizard validation and send
+    #------------------------------------------------------
+
     def send_mail(self, cr, uid, ids, context=None):
-        '''Process the wizard contents and proceed with sending the corresponding
-           email(s), rendering any template patterns on the fly if needed.
-           If the wizard is in mass-mail mode (context['mail.compose.message.mode'] is
-           set to ``'mass_mail'``), the resulting email(s) are scheduled for being
-           sent the next time the mail.message scheduler runs, or the next time
-           ``mail.message.process_email_queue`` is called.
-           Otherwise the new message is sent immediately.
-
-           :param dict context: several context values will modify the behavior
-                                of the wizard, cfr. the class description.
-        '''
+        """ Process the wizard content and proceed with sending the related
+            email(s), rendering any template patterns on the fly if needed. """
         if context is None:
             context = {}
-        # composition wizard options
-        email_mode = context.get('email_mode')
-        formatting = context.get('formatting')
-        mass_mail_mode = context.get('mail.compose.message.mode') == 'mass_mail'
-
-        mail_message_obj = self.pool.get('mail.message')
-        for mail_wiz in self.browse(cr, uid, ids, context=context):
-            # attachments
-            attachment = {}
-            for attach in mail_wiz.attachment_ids:
-                attachment[attach.datas_fname] = attach.datas and attach.datas.decode('base64')
-
-            # default values, according to the wizard options
-            subject = mail_wiz.subject if formatting else False
-            content_subtype = 'html' if formatting else 'plain'
-            type = 'email' if email_mode else 'comment'
-            state = 'outgoing' if email_mode else False
-            partner_ids = [partner.id for partner in mail_wiz.dest_partner_ids]
-            references = None
-            headers = {}
-            body = mail_wiz.body_html if content_subtype == 'html' else mail_wiz.body_text
-
-            # get model, active_ids, and check if model is openchatter-enabled
-            if mass_mail_mode and context.get('active_ids') and context.get('active_model'):
-                active_ids = context['active_ids']
-                active_model = context['active_model']
-            elif mass_mail_mode:
-                active_model = mail_wiz.model
-                active_model_pool = self.pool.get(active_model)
-                active_ids = active_model_pool.search(cr, uid, ast.literal_eval(mail_wiz.filter_id.domain), context=ast.literal_eval(mail_wiz.filter_id.context))
-            else:
-                active_model = mail_wiz.model
-                active_ids = [mail_wiz.res_id]
-            active_model_pool = self.pool.get(active_model)
-            mail_thread_enabled = hasattr(active_model_pool, 'message_append')
-
-            if context.get('mail.compose.message.mode') == 'mass_mail':
-                # Mass mailing: must render the template patterns
-                for active_id in active_ids:
-                    rendered_subject = self.render_template(cr, uid, subject, active_model, active_id)
-                    rendered_body_html = self.render_template(cr, uid, mail_wiz.body_html, active_model, active_id)
-                    rendered_body_text = self.render_template(cr, uid, mail_wiz.body_text, active_model, active_id)
-                    email_from = self.render_template(cr, uid, mail_wiz.email_from, active_model, active_id)
-                    email_to = self.render_template(cr, uid, mail_wiz.email_to, active_model, active_id)
-                    email_cc = self.render_template(cr, uid, mail_wiz.email_cc, active_model, active_id)
-                    email_bcc = self.render_template(cr, uid, mail_wiz.email_bcc, active_model, active_id)
-                    reply_to = self.render_template(cr, uid, mail_wiz.reply_to, active_model, active_id)
-
-                    # in mass-mailing mode we only schedule the mail for sending, it will be 
-                    # processed as soon as the mail scheduler runs.
-                    if mail_thread_enabled:
-                        active_model_pool.message_append(cr, uid, [active_id], rendered_subject, rendered_body_text, rendered_body_html,
-                            type=type, content_subtype=content_subtype, state=state, partner_ids=partner_ids,
-                            email_from=email_from, email_to=email_to, email_cc=email_cc, email_bcc=email_bcc,
-                            reply_to=reply_to, references=references, attachments=attachment, headers=headers, context=context)
-                    else:
-                        mail_message_obj.schedule_with_attach(cr, uid, email_from, to_email(email_to), subject, rendered_body_text,
-                            model=mail_wiz.model, email_cc=to_email(email_cc), email_bcc=to_email(email_bcc), reply_to=reply_to,
-                            attachments=attachment, references=references, res_id=active_id, partner_ids=partner_ids,
-                            content_subtype=mail_wiz.content_subtype, headers=headers, context=context)
-            else:
-                # normal mode - no mass-mailing
-                if mail_thread_enabled:
-                    msg_ids = active_model_pool.message_append(cr, uid, active_ids, subject, mail_wiz.body_text, mail_wiz.body_html,
-                        type=type, content_subtype=content_subtype, state=state, partner_ids=partner_ids,
-                        email_from=mail_wiz.email_from, email_to=mail_wiz.email_to, email_cc=mail_wiz.email_cc, email_bcc=mail_wiz.email_bcc,
-                        reply_to=mail_wiz.reply_to, references=references, attachments=attachment, headers=headers, context=context)
-                else:
-                    msg_ids = [mail_message_obj.schedule_with_attach(cr, uid, mail_wiz.email_from, to_email(mail_wiz.email_to), subject, mail_wiz.body_text,
-                        type=type, model=mail_wiz.model, email_cc=to_email(mail_wiz.email_cc), email_bcc=to_email(mail_wiz.email_bcc), reply_to=mail_wiz.reply_to,
-                        attachments=attachment, references=references, res_id=int(mail_wiz.res_id), partner_ids=partner_ids,
-                        content_subtype=mail_wiz.content_subtype, headers=headers, context=context)]
-                # in normal mode, we send the email immediately, as the user expects us to (delay should be sufficiently small)
-                if type == 'email':
-                    mail_message_obj.send(cr, uid, msg_ids, context=context)
+        active_ids = context.get('active_ids')
+
+        for wizard in self.browse(cr, uid, ids, context=context):
+            mass_mail_mode = wizard.composition_mode == 'mass_mail'
+            active_model_pool = self.pool.get(wizard.model if wizard.model else 'mail.thread')
+
+            # wizard works in batch mode: [res_id] or active_ids
+            res_ids = active_ids if mass_mail_mode and wizard.model and active_ids else [wizard.res_id]
+            for res_id in res_ids:
+                # default values, according to the wizard options
+                post_values = {
+                    'subject': wizard.subject if wizard.content_subtype == 'html' else False,
+                    'body': wizard.body if wizard.content_subtype == 'html' else '<pre>%s</pre>' % tools.ustr(wizard.body_text),
+                    'partner_ids': [(4, partner.id) for partner in wizard.partner_ids],
+                    'attachments': [(attach.datas_fname or attach.name, base64.b64decode(attach.datas)) for attach in wizard.attachment_ids],
+                }
+                # mass mailing: render and override default values
+                if mass_mail_mode and wizard.model:
+                    email_dict = self.render_message(cr, uid, wizard, res_id, context=context)
+                    new_partner_ids = email_dict.pop('partner_ids', [])
+                    post_values['partner_ids'] += [(4, partner_id) for partner_id in new_partner_ids]
+                    new_attachments = email_dict.pop('attachments', [])
+                    post_values['attachments'] += new_attachments
+                    post_values.update(email_dict)
+                # post the message
+                active_model_pool.message_post(cr, uid, [res_id], type='comment', context=context, **post_values)
 
         return {'type': 'ir.actions.act_window_close'}
 
+    def render_message(self, cr, uid, wizard, res_id, context=None):
+        """ Generate an email from the template for given (wizard.model, res_id)
+            pair. This method is meant to be inherited by email_template that
+            will produce a more complete dictionary. """
+        return {
+            'subject': self.render_template(cr, uid, wizard.subject, wizard.model, res_id, context),
+            'body': self.render_template(cr, uid, wizard.body, wizard.model, res_id, context),
+        }
+
     def render_template(self, cr, uid, template, model, res_id, context=None):
-        """Render the given template text, replace mako-like expressions ``${expr}``
-           with the result of evaluating these expressions with an evaluation context
-           containing:
+        """ Render the given template text, replace mako-like expressions ``${expr}``
+            with the result of evaluating these expressions with an evaluation context
+            containing:
 
                 * ``user``: browse_record of the current user
                 * ``object``: browse_record of the document record this mail is
                               related to
                 * ``context``: the context passed to the mail composition wizard
 
-           :param str template: the template text to render
-           :param str model: model name of the document record this mail is related to.
-           :param int res_id: id of the document record this mail is related to.
+            :param str template: the template text to render
+            :param str model: model name of the document record this mail is related to.
+            :param int res_id: id of the document record this mail is related to.
         """
         if context is None:
             context = {}
         def merge(match):
             exp = str(match.group()[2:-1]).strip()
-            result = eval(exp,
-                          {
-                            'user' : self.pool.get('res.users').browse(cr, uid, uid, context=context),
-                            'object' : self.pool.get(model).browse(cr, uid, res_id, context=context),
-                            'context': dict(context), # copy context to prevent side-effects of eval
-                          })
-            if result in (None, False):
-                return ""
-            return tools.ustr(result)
+            result = eval(exp, {
+                'user': self.pool.get('res.users').browse(cr, uid, uid, context=context),
+                'object': self.pool.get(model).browse(cr, uid, res_id, context=context),
+                'context': dict(context), # copy context to prevent side-effects of eval
+                })
+            return result and tools.ustr(result) or ''
         return template and EXPRESSION_PATTERN.sub(merge, template)
-
-
-class mail_compose_message_extended(osv.TransientModel):
-    """ Extension of 'mail.compose.message' to support default field values related
-        to CRM-like models that follow the following conventions:
-
-        1. The model object must have an attribute '_mail_compose_message' equal to True.
-
-        2. The model should define the following fields:
-            - 'name' as subject of the message (required);
-            - 'email_from' as destination email address (required);
-            - 'email_cc' as cc email addresses (required);
-            - 'section_id.reply_to' as reply-to address (optional).
-    """
-    _inherit = 'mail.compose.message'
-
-    def get_value(self, cr, uid, model, res_id, context=None):
-        """ Overrides the default implementation to provide more default field values
-            related to the corresponding CRM case.
-        """
-        result = super(mail_compose_message_extended, self).get_value(cr, uid, model, res_id, context=context)
-        model_obj = self.pool.get(model)
-        if getattr(model_obj, '_mail_compose_message', False) and res_id:
-            data = model_obj.browse(cr, uid , res_id, context)
-            result.update({
-                'email_to': data.email_from or False,
-                'email_cc': tools.ustr(data.email_cc or ''),
-                'subject': data.name or False,
-            })
-            if hasattr(data, 'section_id'):
-                result['reply_to'] = data.section_id and data.section_id.reply_to or False
-        return result
-
-    def onchange_email_mode(self, cr, uid, ids, value, model, res_id, context=None):
-        """ Overrides the default implementation to provide default values for
-            dest_partner_ids. This method checks that a partner maching the
-            ``email_from`` of the record exists. It it does not exist, it
-            creates a new partner. The found or created partner is then added
-            in dest_partner_ids.
-            Partner check/creation valid inly if the value is True, and if 
-            the model has the ``_mail_compose_message`` attribute.
-        """
-        result = super(mail_compose_message_extended, self).onchange_email_mode(cr, uid, ids, value, model, res_id, context=context)
-        model_obj = self.pool.get(model)
-        if not value or not (getattr(model_obj, '_mail_compose_message', False) and res_id):
-            return result
-        data = model_obj.browse(cr, uid , res_id, context=context)
-        partner_obj = self.pool.get('res.partner')
-        partner_ids = partner_obj.search(cr, uid, [('email', '=', data.email_from)], context=context)
-        if partner_ids:
-            partner_id = partner_ids[0]
-        else:
-            partner_id = partner_obj.name_create(cr, uid, data.email_from, context=context)[0]
-        result['value'].update({
-            'dest_partner_ids': [partner_id],
-            'email_cc': tools.ustr(data.email_cc or ''),
-        })
-        if hasattr(data, 'section_id'):
-            result['value']['reply_to'] = data.section_id and data.section_id.reply_to or False
-        return result
-
-    def onchange_formatting(self, cr, uid, ids, value, model, res_id, context=None):
-        """ Overrides the default implementation to provide default values for
-            the subject.
-            Subject re-creation valid only if the value is True, and if the
-            model has the ``_mail_compose_message`` attribute.
-        """
-        result = super(mail_compose_message_extended, self).onchange_formatting(cr, uid, ids, value, model, res_id, context=context)
-        model_obj = self.pool.get(model)
-        if not value or not (getattr(model_obj, '_mail_compose_message', False) and res_id):
-            return result
-        data = model_obj.browse(cr, uid , res_id, context=context)
-        result['value'].update({
-            'subject': data.name or False,
-        })
-        return result
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
index 73fb03c..70b50c1 100644 (file)
@@ -6,73 +6,87 @@
             <field name="model">mail.compose.message</field>
             <field name="arch" type="xml">
                 <form string="Compose Email" version="7.0">
+                    <field name="composition_mode" nolabel="1" invisible="1"/>
+                    <field name="model" nolabel="1" invisible="1"/>
+                    <field name="res_id" nolabel="1" invisible="1"/>
+                    <field name="parent_id" nolabel="1" invisible="1"/>
+                    <field name="content_subtype" nolabel="1" invisible="1"/>
                     <group>
-                        <field name="model" invisible="1"/>
-                        <field name="res_id" invisible="1"/>
-                        <field name='filter_id' invisible="context.get('active_model',False)"/>
-                        <field name="email_from" required="1"/>
-                        <field name="email_to" required="1"/>
-                        <field name="email_cc"/>
-                        <field name="email_bcc"/>
-                        <field name="reply_to"/>
-                        <field name="subject" widget="char" size="512"/>
-                        <field name="references"/>
-                        <field name="message_id"/>
+                        <field name="subject" placeholder="Subject..."/>
+                        <field name="partner_ids" widget="many2many_tags" placeholder="Add contacts to notify..."
+                                context="{'force_create':True}"
+                                on_change="onchange_partner_ids(partner_ids)"/>
                     </group>
-                    <notebook colspan="4">
+                    <notebook>
                         <page string="Body">
-                            <field name="body_text" colspan="4" nolabel="1" height="300" width="300"/>
+                            <field name="body_text" nolabel="1"
+                                attrs="{'invisible':[('content_subtype', '=', 'html')]}"/>
+                            <field name="body" nolabel="1"
+                                attrs="{'invisible':[('content_subtype', '=', 'plain')]}"/>
                         </page>
                         <page string="Attachments">
                             <field name="attachment_ids" colspan="4" nolabel="1"/>
                         </page>
                     </notebook>
                     <footer>
-                        <button name="send_mail" string="Send" type="object" class="oe_highlight"  />
+                        <button string="Send" name="send_mail" type="object" class="oe_highlight"  />
                         or
                         <button string="Cancel" class="oe_link" special="cancel" />
+                        <div class="oe_right">
+                            <button string="" name="toggle_content_subtype" type="object" icon="/mail/static/src/img/formatting.png" 
+                                help="Toggle advanced formatting mode"/>
+                        </div>
                     </footer>
                 </form>
             </field>
         </record>
 
         <record model="ir.ui.view" id="email_compose_message_wizard_form_chatter">
-            <field name="name">mail.compose.message.form</field>
+            <field name="name">mail.compose.message.form.chatter</field>
             <field name="model">mail.compose.message</field>
             <field name="priority">18</field>
             <field name="arch" type="xml">
                 <form string="Compose Email" version="7.0" >
                     <group>
+                        <!-- truly invisible fields for control and options -->
+                        <field name="composition_mode" colspan="2" nolabel="1" invisible="1"/>
+                        <field name="model" colspan="2" nolabel="1" invisible="1"/>
+                        <field name="res_id" colspan="2" nolabel="1" invisible="1"/>
+                        <field name="parent_id" colspan="2" nolabel="1" invisible="1"/>
+                        <field name="content_subtype" colspan="2" nolabel="1" invisible="1"/>
+                        <!-- visible wizard -->
                         <field name="subject" colspan="2" nolabel="1" placeholder="Subject..."
-                                class="oe_mail_compose_message_subject oe_mail_compose_message_invisible"/>
+                                class="oe_mail_compose_message_subject"
+                                attrs="{'invisible':[('content_subtype', '=', 'plain')]}"/>
                         <field name="body_text" colspan="2" nolabel="1" placeholder="What are you working on ?"
-                                class="oe_mail_compose_message_body_text"/>
-                        <field name="body_html" colspan="2" nolabel="1" placeholder="What are you working on HTML ?"
-                                class="oe_mail_compose_message_body_html oe_mail_compose_message_invisible"/>
-                        <field name="dest_partner_ids" colspan="2" nolabel="1" widget="many2many_tags"
-                                placeholder="Add contacts to notify..."
-                                class="oe_mail_compose_message_partner_ids oe_mail_compose_message_invisible"/>
+                                class="oe_mail_compose_message_body"
+                                attrs="{'invisible':[('content_subtype', '=', 'html')]}"/>
+                        <field name="body" colspan="2" nolabel="1" placeholder="What are you working on ?"
+                                class="oe_mail_compose_message_body_html"
+                                attrs="{'invisible':[('content_subtype', '=', 'plain')]}"/>
+                        <field name="partner_ids" colspan="2" nolabel="1" widget="many2many_tags" placeholder="Add contacts to notify..."
+                                context="{'force_create':True}"
+                                on_change="onchange_partner_ids(partner_ids)"
+                                class="oe_mail_compose_message_partner_ids"/>
+                        <field name="attachment_ids" colspan="2" nolabel="1" widget="many2many_tags"
+                                placeholder="Add attachments..." invisible="1"
+                                class="oe_mail_compose_message_attachment_ids"/>
+                        <!-- void div to display attachments, Chatter-controlled -->
+                        <div colspan="2" class="oe_mail_compose_message_attachments"/>
+                        <!-- buttons, with as few Chatter logic as possible -->
                         <div>
                             <button name="send_mail" string="Post" type="object"
                                 class="oe_mail_compose_message_button_send"/>
-                            or <a href="#" class="oe_mail_compose_message_email">Send an Email</a>
                         </div>
                         <div class='oe_mail_compose_message_icons'>
-<!--                             <button icon="../../../../../mail/static/src/img/checklist"
-                                type="object" name="checklist" string=""
-                                help="Add a checklist"/> -->
-                            <a href="#" class="oe_mail_compose_message_checklist">
-                                <img src='/mail/static/src/img/checklist.png/' alt='Checklist'
-                                    title='Add a checklist'/>
-                            </a>
-                            <a href="#" class="oe_mail_compose_message_attachment">
-                                <img src='/mail/static/src/img/attachment.png/' alt='Attachment'
-                                    title='Add an attachment'/>
-                            </a>
-                            <a href="#" class="oe_mail_compose_message_formatting">
-                                <img src='/mail/static/src/img/formatting.png/' alt='Formatting'
-                                    title='Switch to advanced formatting mode'/>
-                            </a>
+                            <button icon="/mail/static/src/img/attachment.png"
+                                class="oe_mail_compose_message_attachment" string=""
+                                name="dummy"
+                                help="Add an attachment"/>
+                            <button icon="/mail/static/src/img/formatting.png"
+                                class="oe_mail_compose_message_formatting" string=""
+                                type="object" name="toggle_content_subtype"
+                                help="Toggle advanced formatting mode"/>
                         </div>
                     </group>
                 </form>
                 target="new"
                 key2="client_action_multi"
                 id="base.action_partner_mass_mail"
-                context="{'mail.compose.message.mode':'mass_mail'}"/>
+                context="{'default_composition_mode': 'mass_mail'}"/>
     </data>
 </openerp>
index 54897cd..92443fe 100644 (file)
@@ -3,29 +3,29 @@
     <data>
         <!-- Email tempalte -->
         <record id="email_template_1" model="email.template">
-            <field name="name">Template for New Partner</field>
+            <field name="name">welcome new partner</field>
             <field name="email_from">info@openerp.com</field>
-            <field name="subject">Welcome in OpenERP Partner Channel!</field>
+            <field name="subject">Welcome to the OpenERP Partner Channel!</field>
             <field name="email_to">${object.email or ''}</field>
             <field name="model_id" ref="base.model_res_partner"/>
-            <field name="body_text">Hello, We are very happy to send Welcome message.</field>
+            <field name="body_html">Hello, you will receive your welcome pack via email shortly.</field>
         </record>
         <record id="email_template_2" model="email.template">
-            <field name="name">Template for Silver Partner</field>
+            <field name="name">congrats silver partner</field>
             <field name="email_from">info@openerp.com</field>
-            <field name="subject">Congratulation! You become now our Silver Partner.</field>
+            <field name="subject">Congratulations! You are now a Silver Partner!</field>
             <field name="email_to">${object.email or ''}</field>
             <field name="model_id" ref="base.model_res_partner"/>
-            <field name="body_text">Hello, We are happy to announce that you now become our Silver Partner.</field>
+            <field name="body_html">Hi, we are delighted to welcome you among our Silver Partners as of today!</field>
         </record>
 
         <record id="email_template_3" model="email.template">
-            <field name="name">Template for Gold Partner</field>
+            <field name="name">congrats gold partner</field>
             <field name="email_from">info@openerp.com</field>
-            <field name="subject">Congratulation! You become our Gold Partner.</field>
+            <field name="subject">Congratulations! You are now one of our Gold Partners!</field>
             <field name="email_to">${object.email or ''}</field>
             <field name="model_id" ref="base.model_res_partner"/>
-            <field name="body_text">Hello, We are happy to announce that you become our Gold Partner.</field>
+            <field name="body_html">Hi, we are delighted to let you know that you have entered the select circle of our Gold Partners</field>
         </record>
 
 
@@ -64,7 +64,7 @@
         </record>
 
 
-    <!-- Tranisition -->
+    <!-- Transition -->
         <record id="marketing_campaign_transition_0" model="marketing.campaign.transition">
             <field model="marketing.campaign.activity" name="activity_from_id" ref = "marketing_campaign_activity_0"/>
             <field model="marketing.campaign.activity" name="activity_to_id" ref = "marketing_campaign_activity_1"/>
index 04063a6..3d540b4 100644 (file)
@@ -42,7 +42,7 @@
     record = self.browse(cr, uid, ids[0])
     assert record.state == 'todo' or record.state == 'done' , 'Marketing Workitem shoud be in draft state.'
 -
-  I check follow-up detail of first activity.
+  I process follow-up of first activity.
 -
   !python {model: marketing.campaign.workitem}: |
     ids = self.search(cr, uid, [('segment_id', '=', ref('marketing_campaign_segment0')),
     assert ids, 'Follow-up item is not created for first activity.'
     work_item_id = self.browse(cr ,uid ,ids[0] ,context)
     assert work_item_id.res_name, 'Resource Name is not defined.'
--
-  I process follow-up of first activity.
--
-  !python {model: marketing.campaign.workitem}: |
-    ids = self.search(cr, uid, [('segment_id', '=', ref('marketing_campaign_segment0')),
-    ('campaign_id', '=', ref('marketing_campaign_openerppartnerchannel')), ('activity_id', '=', ref('marketing_campaign_activity_0'))])
     self.process(cr, uid, ids)
     record = self.browse(cr, uid, ids)[0]
     assert record.state == "done", "Follow-up item should be closed after process."
index 0946ff6..141981a 100644 (file)
             <field model="ir.values" name="ref_ir_value" search="[('name', '=', u'Send Mail (For OpenERP OnDemand Free Trial 2010)')]"/>
             <field name="model_id" ref="crm.model_crm_lead"/>
             <field eval="0" name="user_signature"/>
-            <field name="body_text">Hello,Thanks for generous interest you have shown in the openERP.Regards,OpenERP Team,</field>
+            <field name="body_html"><![CDATA[<p>Hello,</p>
+            <p>Thanks for the genuine interest you have shown in OpenERP.</p>
+            <p>If any further information is required, do not hesitate to reply to this message.</p>
+            <p>Regards,OpenERP Team,</p>]]></field>
             <field name="name">For OpenERP OnDemand Free Trial 2010</field>
         </record>
         <record id="email_template_2" model="email.template">
             <field model="ir.values" name="ref_ir_value" search="[('name', '=', u'Send Mail (For OpenERP OnDemand Free Trial 2010)')]"/>
             <field name="model_id" ref="crm.model_crm_lead"/>
             <field eval="0" name="user_signature"/>
-            <field name="body_text">Hello,We have very good offer that might suit you.
-            We propose you to subscribe to the OpenERP Discovery Day on May 2010.
-            If any further information required kindly revert back.
-            We really appreciate your co-operation on this.
-            Regards,OpenERP Team,</field>
+            <field name="body_html"><![CDATA[<p>Hello,</p>
+            <p>We have very good offer that might suit you.
+            We suggest you subscribe to the OpenERP Discovery Day on May 2010.</p>
+            <p>If any further information is required, do not hesitate to reply to this message.</p>
+            <p>Regards,OpenERP Team,</p>]]></field>
             <field name="name">For OpenERP Discovery Day on May 2010</field>
         </record>
 
             <field model="ir.values" name="ref_ir_value" search="[('name', '=', u'Send Mail (For OpenERP OnDemand Free Trial 2010)')]"/>
             <field name="model_id" ref="crm.model_crm_lead"/>
             <field eval="0" name="user_signature"/>
-            <field name="body_text">Hello,Thanks for showing intrest and for subscribing to the OpenERP Discovery Day.
-             If any further information required kindly revert back.
-             I really appreciate your co-operation on this.
-             Regards,OpenERP Team,</field>
+            <field name="body_html"><![CDATA[<p>Hello,</p>
+            <p>Thanks for showing interest and for subscribing to the OpenERP Discovery Day.</p>
+            <p>If any further information is required, do not hesitate to reply to this message.</p>
+            <p>Regards,OpenERP Team,</p>]]></field>
             <field name="name">For OpenERP Discovery Day</field>
         </record>
 
            <field model="ir.values" name="ref_ir_value" search="[('name', '=', u'Send Mail (For OpenERP OnDemand Free Trial 2010)')]"/>
              <field name="model_id" ref="crm.model_crm_lead"/>
             <field eval="0" name="user_signature"/>
-            <field name="body_text">Hello,Thanks for showing intrest and buying the OpenERP book.
+            <field name="body_html"><![CDATA[<p>Hello,</p>
+            <p>Thanks for showing interest and buying the OpenERP book.</p>
             If any further information required kindly revert back.
-            I really appreciate your co-operation on this.
-            Regards,OpenERP Team,</field>
+            <p>Regards,OpenERP Team,</p>]]></field>
             <field name="name">For OpenERP book</field>
         </record>
 
             <field model="ir.actions.act_window" name="ref_ir_act_window" search="[('name', '=', u'For OpenERP OnDemand Free Trial 2010 Mail Form')]"/>
             <field model="ir.values" name="ref_ir_value" search="[('name', '=', u'Send Mail (For OpenERP OnDemand Free Trial 2010)')]"/>
             <field eval="0" name="user_signature"/>
-            <field name="body_text">Hello, We have very good offer that might suit you.
-            For our gold partners,We are arranging free technical training on june,2010.
-            If any further information required kindly revert back.
-            I really appreciate your co-operation on this.
-            Regards,OpenERP Team,</field>
-            <field name="name">For technical training to Gold partners</field>
+            <field name="body_html"><![CDATA[<p>Hello,</p>
+            <p>We have very good offer that might suit you.
+            For our gold partners,We are arranging free technical training on june,2010.</p>
+            <p>If any further information is required, do not hesitate to reply to this message.</p>
+            <p>Regards,OpenERP Team,</p>]]></field>
+            <field name="name">technical training to gold partners</field>
         </record>
 
           <record id="email_template_6" model="email.template">
             <field model="ir.values" name="ref_ir_value" search="[('name', '=', u'Send Mail (For OpenERP OnDemand Free Trial 2010)')]"/>
             <field name="model_id" ref="crm.model_crm_lead"/>
             <field eval="0" name="user_signature"/>
-            <field name="body_text">Hello, We have very good offer that might suit you.
-            For our silver partners,We are  paid technical training on june,2010.
-            If any further information required kindly revert back.
-            I really appreciate your co-operation on this.
-            Regards,OpenERP Team,</field>
-            <field name="name">For training to Silver partners</field>
+            <field name="body_html"><![CDATA[<p>Hello,</p>
+            <p>We have very good offer that might suit you.
+            For our silver partners,We are  paid technical training on june,2010.</p>
+            <p>If any further information is required, do not hesitate to reply to this message.</p>
+            <p>Regards,OpenERP Team,</p>]]></field>
+            <field name="name">training to silver partners</field>
         </record>
 
         <record id="email_template_7" model="email.template">
             <field model="ir.values" name="ref_ir_value" search="[('name', '=', u'Send Mail (For OpenERP OnDemand Free Trial 2010)')]"/>
             <field name="model_id" ref="crm.model_crm_lead"/>
             <field eval="0" name="user_signature"/>
-            <field name="body_text">Hello, We have very good offer that might suit you.
-            For our silver partners,We are  offering Gold partnership.
-            If any further information required kindly revert back.
-            I really appreciate your co-operation on this.
-            Regards,OpenERP Team,</field>
-            <field name="name">For gold partnership to silver partners</field>
+            <field name="body_html"><![CDATA[<p>Hello,</p>
+            <p>We have very good offer that might suit you.
+            For our silver partners, we are  offering Gold partnership.</p>
+            <p>If any further information is required, do not hesitate to reply to this message.</p>
+            <p>Regards,OpenERP Team,</p>]]></field>
+            <field name="name">gold partnership to silver partners</field>
         </record>
 
         <record id="email_template_8" model="email.template">
             <field model="ir.values" name="ref_ir_value" search="[('name', '=', u'Send Mail (For OpenERP OnDemand Free Trial 2010)')]"/>
             <field name="model_id" ref="crm.model_crm_lead"/>
             <field eval="0" name="user_signature"/>
-            <field name="body_text">Hello, Thanks for showing intrest and for subscribing to technical training.If any further information required kindly revert back.I really appreciate your co-operation on this.
-            Regards,OpenERP Team,</field>
-            <field name="name">For subscribing to technical training</field>
+            <field name="body_html"><![CDATA[<p>Hello,</p>
+            <p>Thanks for showing interest and for subscribing to technical training.</p>
+            If any further information required kindly revert back.I really appreciate your co-operation on this.</p>
+            <p>If any further information is required, do not hesitate to reply to this message.</p>
+            <p>Regards,OpenERP Team,</p>]]></field>
+            <field name="name">subscribing to technical training</field>
         </record>
         <!-- Report -->
 
index 7b8442b..c667daa 100644 (file)
@@ -369,7 +369,7 @@ class mrp_bom(osv.osv):
         prod_obj = self.pool.get('product.product')
         for obj in self.browse(cr, uid, ids, context=context):
             for prod in prod_obj.browse(cr, uid, [obj.product_id], context=context):
-                self.message_append_note(cr, uid, [obj.id], body=_("Bill of Material has been <b>created</b> for <em>%s</em> product.") % (prod.id.name_template), context=context)
+                self.message_post(cr, uid, [obj.id], body=_("Bill of Material has been <b>created</b> for <em>%s</em> product.") % (prod.id.name_template), context=context)
         return True
 
 mrp_bom()
@@ -407,7 +407,7 @@ class mrp_production(osv.osv):
     _name = 'mrp.production'
     _description = 'Manufacturing Order'
     _date_name  = 'date_planned'
-    _inherit = ['ir.needaction_mixin', 'mail.thread']
+    _inherit = ['mail.thread', 'ir.needaction_mixin']
 
     def _production_calc(self, cr, uid, ids, prop, unknow_none, context=None):
         """ Calculates total hours and total no. of cycles for a production order.
@@ -1046,33 +1046,28 @@ class mrp_production(osv.osv):
     # OpenChatter methods and notifications
     # ---------------------------------------------------
 
-    def message_get_monitored_follower_fields(self, cr, uid, ids, context=None):
-        """ Add 'user_id' to the monitored fields """
-        res = super(mrp_production, self).message_get_monitored_follower_fields(cr, uid, ids, context=context)
-        return res + ['user_id']
-
     def create_send_note(self, cr, uid, ids, context=None):
-        self.message_append_note(cr, uid, ids, body=_("Manufacturing order has been <b>created</b>."), context=context)
+        self.message_post(cr, uid, ids, body=_("Manufacturing order has been <b>created</b>."), context=context)
         return True
 
     def action_cancel_send_note(self, cr, uid, ids, context=None):
         message = _("Manufacturing order has been <b>canceled</b>.")
-        self.message_append_note(cr, uid, ids, body=message, context=context)
+        self.message_post(cr, uid, ids, body=message, context=context)
         return True
 
     def action_ready_send_note(self, cr, uid, ids, context=None):
         message = _("Manufacturing order is <b>ready to produce</b>.")
-        self.message_append_note(cr, uid, ids, body=message, context=context)
+        self.message_post(cr, uid, ids, body=message, context=context)
         return True
 
     def action_in_production_send_note(self, cr, uid, ids, context=None):
         message = _("Manufacturing order is <b>in production</b>.")
-        self.message_append_note(cr, uid, ids, body=message, context=context)
+        self.message_post(cr, uid, ids, body=message, context=context)
         return True
 
     def action_done_send_note(self, cr, uid, ids, context=None):
         message = _("Manufacturing order has been <b>done</b>.")
-        self.message_append_note(cr, uid, ids, body=message, context=context)
+        self.message_post(cr, uid, ids, body=message, context=context)
         return True
 
     def action_confirm_send_note(self, cr, uid, ids, context=None):
@@ -1082,7 +1077,7 @@ class mrp_production(osv.osv):
             obj_datetime = fields.DT.datetime.strptime(obj.date_planned, DEFAULT_SERVER_DATETIME_FORMAT)
             obj_date_str = fields.datetime.context_timestamp(cr, uid, obj_datetime, context=context).strftime(DATETIME_FORMATS_MAP['%+'] + " (%Z)")
             message = _("Manufacturing order has been <b>confirmed</b> and is <b>scheduled</b> for the <em>%s</em>.") % (obj_date_str)
-            self.message_append_note(cr, uid, [obj.id], body=message, context=context)
+            self.message_post(cr, uid, [obj.id], body=message, context=context)
         return True
 
 
index e5d5380..0bbf2d3 100644 (file)
@@ -2,14 +2,16 @@
 <openerp>
     <data noupdate="1">
         <!-- notify all employees of module installation -->
-        <function model="mail.group" name="message_append_note">
-            <!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
-            <value eval="[ref('mail.group_all_employees')]"/>
-            <value>Module MRP has been installed</value>
-            <value>Manage your manufacturing process in OpenERP by defining bill of materials (BoM), routings and work centers.  This module supports complete integration and planification of stockable goods, consumable, and services.
+        <record model="mail.message" id="module_install_notification">
+            <field name="model">mail.group</field>
+            <field name="res_id" ref="mail.group_all_employees"/>
+            <field name="type">notification</field>
+            <field name="subject">MRP application installed!</field>
+            <field name="body">Manage your manufacturing process with OpenERP by defining your bills of materials (BoM), routings and work centers.
+This application supports complete integration and production scheduling for stockable goods, consumables, and services.
 
-From the Manufacturing Settings, you can choose to compute production schedules periodically or just-in-time.</value>
-        </function>
+From the Manufacturing Settings, you can choose to compute production schedules periodically or just-in-time.</field>
+        </record>
         
         <record id="sequence_mrp_prod_type" model="ir.sequence.type">
             <field name="name">Production order</field>
index d0f05a2..952a5e0 100644 (file)
@@ -49,7 +49,7 @@
             <field name="costs_hour_account_id" ref="account_assembly_hours"/>
             <field name="costs_cycle">0.05</field>
             <field name="costs_cycle_account_id" ref="account_assembly_cycle"/>
-            <field name="costs_journal_id" ref="account.expenses_journal"/>
+            <field name="costs_journal_id" ref="account.exp"/>
             <field name="costs_general_account_id" ref="account.a_expense"/>
             <field name="costs_general_account_id" ref="account.a_expense"/>
             <field name="calendar_id" ref="resource.timesheet_group1"/>
index acb51ea..8121f6e 100644 (file)
             <field name="name">mrp.production.tree</field>
             <field name="model">mrp.production</field>
             <field name="arch" type="xml">
-                <tree fonts="bold:needaction_pending==True" colors="blue:state in ('draft','confirmed');red:date_planned&lt;current_date and state not in ('done','cancel');black:date_planned&gt;=current_date;gray:state in ('done','cancel') " string="Manufacturing Orders">
-                    <field name="needaction_pending" invisible="1"/>
+                <tree fonts="bold:message_unread==True" colors="blue:state in ('draft','confirmed');red:date_planned&lt;current_date and state not in ('done','cancel');black:date_planned&gt;=current_date;gray:state in ('done','cancel') " string="Manufacturing Orders">
+                    <field name="message_unread" invisible="1"/>
                     <field name="name"/>
                     <field name="date_planned"/>
                     <field name="product_id"/>
index 363f98e..7f417dc 100644 (file)
@@ -43,7 +43,7 @@ class procurement_order(osv.osv):
             cr.execute('update procurement_order set message=%s where id=%s', (_('No BoM defined for this product !'), procurement.id))
             for (id, name) in self.name_get(cr, uid, procurement.id):
                 message = _("Procurement '%s' has an exception: 'No BoM defined for this product !'") % name
-                self.message_append_note(cr, uid, [procurement.id], body=message, context=context)
+                self.message_post(cr, uid, [procurement.id], body=message, context=context)
             return False
         return True
     
index 8692dab..ee2547b 100644 (file)
@@ -224,7 +224,7 @@ class mrp_production_workcenter_line(osv.osv):
         for workorder in self.browse(cr, uid, ids):
             for prod in prod_obj.browse(cr, uid, [workorder.production_id]):
                 message = _("Work order has been <b>created</b> for production order <em>%s</em>.") % (prod.id.name)
-                self.message_append_note(cr, uid, [workorder.id], body=message, context=context)
+                self.message_post(cr, uid, [workorder.id], body=message, context=context)
         return True
 
     def action_start_send_note(self, cr, uid, ids, context=None):
@@ -232,7 +232,7 @@ class mrp_production_workcenter_line(osv.osv):
         for workorder in self.browse(cr, uid, ids):
             for prod in prod_obj.browse(cr, uid, [workorder.production_id]):
                 message = _("Work order has been <b>started</b> for production order <em>%s</em>.") % (prod.id.name)
-                self.message_append_note(cr, uid, [workorder.id], body=message, context=context)
+                self.message_post(cr, uid, [workorder.id], body=message, context=context)
         return True
 
     def action_done_send_note(self, cr, uid, ids, context=None):
@@ -240,7 +240,7 @@ class mrp_production_workcenter_line(osv.osv):
         for workorder in self.browse(cr, uid, ids):
             for prod in prod_obj.browse(cr, uid, [workorder.production_id]):
                 message = _("Work order has been <b>done</b> for production order <em>%s</em>.") % (prod.id.name)
-                self.message_append_note(cr, uid, [workorder.id], body=message, context=context)
+                self.message_post(cr, uid, [workorder.id], body=message, context=context)
         return True
 
     def action_pending_send_note(self, cr, uid, ids, context=None):
@@ -248,7 +248,7 @@ class mrp_production_workcenter_line(osv.osv):
         for workorder in self.browse(cr, uid, ids):
             for prod in prod_obj.browse(cr, uid, [workorder.production_id]):
                 message = _("Work order is <b>pending</b> for production order <em>%s</em>.") % (prod.id.name)
-                self.message_append_note(cr, uid, [workorder.id], body=message, context=context)
+                self.message_post(cr, uid, [workorder.id], body=message, context=context)
         return True
 
     def action_cancel_send_note(self, cr, uid, ids, context=None):
@@ -256,7 +256,7 @@ class mrp_production_workcenter_line(osv.osv):
         for workorder in self.browse(cr, uid, ids):
             for prod in prod_obj.browse(cr, uid, [workorder.production_id]):
                 message = _("Work order has been <b>cancelled</b> for production order <em>%s</em>.") % (prod.id.name)
-                self.message_append_note(cr, uid, [workorder.id], body=message, context=context)
+                self.message_post(cr, uid, [workorder.id], body=message, context=context)
         return True
 
 mrp_production_workcenter_line()
index f41abb5..c1ac8f8 100644 (file)
@@ -571,40 +571,40 @@ class mrp_repair(osv.osv):
     def create_send_note(self, cr, uid, ids, context=None):
         for repair in self.browse(cr, uid, ids, context):
             message = _("Repair Order for <em>%s</em> has been <b>created</b>." % (repair.product_id.name))
-            self.message_append_note(cr, uid, [repair.id], body=message, context=context)
+            self.message_post(cr, uid, [repair.id], body=message, context=context)
         return True
 
     def set_start_send_note(self, cr, uid, ids, context=None):
         for repair in self.browse(cr, uid, ids, context):
             message = _("Repair Order for <em>%s</em> has been <b>started</b>." % (repair.product_id.name))
-            self.message_append_note(cr, uid, [repair.id], body=message, context=context)
+            self.message_post(cr, uid, [repair.id], body=message, context=context)
         return True
 
     def set_toinvoiced_send_note(self, cr, uid, ids, context=None):
         for repair in self.browse(cr, uid, ids, context):
             message = _("Draft Invoice of %s %s <b>waiting for validation</b>.") % (repair.invoice_id.amount_total, repair.invoice_id.currency_id.symbol)
-            self.message_append_note(cr, uid, [repair.id], body=message, context=context)
+            self.message_post(cr, uid, [repair.id], body=message, context=context)
         return True
 
     def set_confirm_send_note(self, cr, uid, ids, context=None):
         for repair in self.browse(cr, uid, ids, context):
             message = _( "Repair Order for <em>%s</em> has been <b>accepted</b>." % (repair.product_id.name))
-            self.message_append_note(cr, uid, [repair.id], body=message, context=context)
+            self.message_post(cr, uid, [repair.id], body=message, context=context)
         return True
 
     def set_cancel_send_note(self, cr, uid, ids, context=None):
         message = _("Repair has been <b>cancelled</b>.")
-        self.message_append_note(cr, uid, ids, body=message, context=context)
+        self.message_post(cr, uid, ids, body=message, context=context)
         return True
 
     def set_ready_send_note(self, cr, uid, ids, context=None):
         message = _("Repair Order is now <b>ready</b> to repair.")
-        self.message_append_note(cr, uid, ids, body=message, context=context)
+        self.message_post(cr, uid, ids, body=message, context=context)
         return True
 
     def set_done_send_note(self, cr, uid, ids, context=None):
         message = _("Repair Order is <b>closed</b>.")
-        self.message_append_note(cr, uid, ids, body=message, context=context)
+        self.message_post(cr, uid, ids, body=message, context=context)
         return True
 
 mrp_repair()
index e497f90..9c582a8 100644 (file)
@@ -44,7 +44,7 @@ class plugin_handler(osv.osv_memory):
         res_id = 0
         url = ""
         name = ""
-        msg = mail_message_obj.parse_message(email)
+        msg = self.pool.get('mail.thread').parse_message(cr, uid, email)
         references = [msg.get('message-id')]
         refs =  msg.get('references',False)
         if refs:
@@ -91,7 +91,7 @@ class plugin_handler(osv.osv_memory):
         """
         mail_message = self.pool.get('mail.message')
         model_obj = self.pool.get(model)
-        msg = mail_message.parse_message(email)
+        msg = self.pool.get('mail.thread').parse_message(cr, uid, email)
         message_id = msg.get('message-id')
         mail_ids = mail_message.search(cr, uid, [('message_id','=',message_id),('res_id','=',res_id),('model','=',model)])
         
@@ -101,16 +101,15 @@ class plugin_handler(osv.osv_memory):
             notify = "Email already pushed"
         elif res_id == 0:
             if model == 'res.partner':
-                notify = 'User the button Partner to create a new partner'
+                notify = 'User the Partner button to create a new partner'
             else:
                 res_id = model_obj.message_new(cr, uid, msg)
-                notify = "Mail succefully pushed, a new %s has been created " % model
+                notify = "Mail succesfully pushed, a new %s has been created " % model
         else:
             if model == 'res.partner':
                 model_obj = self.pool.get('mail.thread')
-            res = self.pool.get(model).browse(cr, uid, [res_id])
-            model_obj.message_append_dict(cr, uid, res, msg)
-            notify = "Mail succefully pushed"
+            model_obj.message_post(cr, uid, [res_id], body=msg)
+            notify = "Mail succesfully pushed"
             
         url = self._make_url(cr, uid, res_id, model)
         return (model, res_id, url, notify)
@@ -133,7 +132,7 @@ class plugin_handler(osv.osv_memory):
         return ('res.partner', partner_id, url)
 
     # Specific to outlook rfc822 is not available so we split in arguments headerd,body,attachemnts
-    def push_message_outlook(self, cr, uid, model, headers,res_id=0 ,body_text=False, body_html=False, attachments=False):
+    def push_message_outlook(self, cr, uid, model, headers,res_id=0 ,body=False, body_html=False, attachments=False):
         # ----------------------------------------
         # solution 1
         # construct a fake rfc822 from the separated arguement
@@ -147,7 +146,7 @@ class plugin_handler(osv.osv_memory):
         mail_message = self.pool.get('mail.message')        
         ir_attachment_obj = self.pool.get('ir.attachment')
         attach_ids = []
-        msg = mail_message.parse_message(headers)
+        msg = self.pool.get('mail.thread').parse_message(cr, uid, headers)
         message_id = msg.get('message-id')    
         push_mail = self.push_message(cr, uid, model, headers, res_id)
         res_id = push_mail[1]
@@ -161,6 +160,6 @@ class plugin_handler(osv.osv_memory):
                 attach_ids.append(ir_attachment_obj.create(cr, uid, vals))
         mail_ids = mail_message.search(cr, uid, [('message_id','=',message_id),('res_id','=',res_id),('model','=',model)])
         if mail_ids:
-            ids =  mail_message.write(cr, uid,mail_ids[0],{ 'attachment_ids': [(6, 0, attach_ids)],'body_text':body_text,'body_html':body_html})
+            ids =  mail_message.write(cr, uid,mail_ids[0],{ 'attachment_ids': [(6, 0, attach_ids)],'body':body,'body_html':body_html})
         url = self._make_url(cr, uid, res_id, model)
         return (model, res_id, url)
index dda0b78..35a2516 100644 (file)
 
     <data noupdate="1">
         <!-- notify all employees of module installation -->
-        <function model="mail.group" name="message_append_note">
-            <!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
-            <value eval="[ref('mail.group_all_employees')]"/>
-            <value>Module Point of Sale has been installed</value>
-            <value>Encode sale orders, register payments, compute money to return, create invoices, and manage refunds of former sales through a specific, web-based, touch-screen user interface.
+        <record model="mail.message" id="module_install_notification">
+            <field name="model">mail.group</field>
+            <field name="res_id" ref="mail.group_all_employees"/>
+            <field name="type">notification</field>
+            <field name="subject">Point of Sale application installed!</field>
+            <field name="body">Record sale orders, register payments, compute change to return, create invoices, and manage refunds through a specific web touch-screen interface.
 
-If you install the PoS proxy, you will be able to interface OpenERP with retail materials; barcode scanners, printers, cash registers, weighing machine, credit card payments.</value>
-        </function>
+If you install the PoS proxy you will be able to interface OpenERP with retail hardware: barcode scanners, printers, cash registers, weighing machines, credit card payment terminals.</field>
+        </record>
 
         <record id="unreferenced_product" model="product.product">
           <field name="list_price">1.00</field>
index a07709c..1ace02f 100644 (file)
@@ -22,8 +22,7 @@
             <field name="subject">Our first company's blogpost !</field>
             <field name="model">mail.group</field>
             <field name="res_id" ref="company_news_feed"/>
-            <field name="content_subtype">html</field>
-            <field name="body_html"><![CDATA[Hello, and welcome to our company's portal !
+            <field name="body"><![CDATA[Hello, and welcome to our company's portal !
 
 Lorem ipsum <b>sit amet</b>, consectetur <em>adipiscing elit</em>. Pellentesque et quam sapien, in sagittis tellus.
 Praesent vel massa sed massa consequat egestas in tristique orci. Praesent iaculis libero et neque vehicula iaculis. Vivamus placerat tincidunt orci ac ornare. Proin ut dolor fringilla velit ultricies consequat. Maecenas sit amet ipsum non leo interdum imperdiet. Donec sapien mi.
@@ -32,27 +31,25 @@ Fusce tempus elit volutpat mi auctor adipiscing. Nam congue luctus suscipit. Sed
 
 Nulla turpis leo, rhoncus ut egestas sit amet, consectetur vitae urna. Mauris in dolor in sapien tempus vehicula.]]></field>
             <field name="type">comment</field>
-            <field name="user_id" ref="base.user_root"/>
+            <field name="author_id" ref="base.partner_root"/>
         </record>
 
         <record id="message_company_news0_comment0" model="mail.message">
             <field name="model">mail.group</field>
             <field name="res_id" ref="company_news_feed"/>
-            <field name="content_subtype">html</field>
-            <field name="body_html"><![CDATA[Great first blogpost !  (first comment)]]></field>
+            <field name="body"><![CDATA[Great first blogpost !  (first comment)]]></field>
             <field name="parent_id" ref="message_company_news0"/>
             <field name="type">comment</field>
-            <field name="user_id" ref="base.user_demo"/>
+            <field name="author_id" ref="base.res_partner_1"/>
         </record>
 
         <record id="message_company_news0_comment1" model="mail.message">
             <field name="model">mail.group</field>
             <field name="res_id" ref="company_news_feed"/>
-            <field name="content_subtype">html</field>
-            <field name="body_html"><![CDATA[Thanks !  (second comment)]]></field>
+            <field name="body"><![CDATA[Thanks !  (second comment)]]></field>
             <field name="parent_id" ref="message_company_news0"/>
             <field name="type">comment</field>
-            <field name="user_id" ref="base.user_root"/>
+            <field name="author_id" ref="base.partner_demo"/>
         </record>
 
     </data>
index 0260ce3..85f6566 100644 (file)
@@ -171,7 +171,7 @@ class wizard(osv.osv_memory):
                 'url': wiz.portal_id.url or _("(missing url)"),
                 'db': cr.dbname,
             }
-            mail_message_obj = self.pool.get('mail.message')
+            mail_mail_obj = self.pool.get('mail.mail')
             dest_uids = user_obj.search(cr, ROOT_UID, login_cond)
             dest_users = user_obj.browse(cr, ROOT_UID, dest_uids)
             for dest_user in dest_users:
@@ -184,10 +184,12 @@ class wizard(osv.osv_memory):
                 email_to = dest_user.email
                 subject = _(WELCOME_EMAIL_SUBJECT) % data
                 body = _(WELCOME_EMAIL_BODY) % data
-                res = mail_message_obj.schedule_with_attach(cr, uid, email_from , [email_to], subject, body, context=context)
-                if not res:
-                    _logger.warning(
-                        'Failed to send email from %s to %s', email_from, email_to)
+                mail_id = mail_mail_obj.create(cr, uid, {
+                            'email_from': email_from ,
+                            'email_to': email_to,
+                            'subject': subject,
+                            'state': 'outgoing',
+                            'body_html': '<pre>%s</pre>' % body}, context=context)
         
         return {'type': 'ir.actions.act_window_close'}
 
index d1fb3f8..db50ebd 100644 (file)
@@ -289,14 +289,14 @@ class procurement_order(osv.osv):
                 return False
             if not procurement.product_id.seller_ids:
                 message = _('No supplier defined for this product !')
-                self.message_append_note(cr, uid, [procurement.id], body=message)
+                self.message_post(cr, uid, [procurement.id], body=message)
                 cr.execute('update procurement_order set message=%s where id=%s', (message, procurement.id))
                 return False
             partner = procurement.product_id.seller_id #Taken Main Supplier of Product of Procurement.
 
             if not partner:
                 message = _('No default supplier defined for this product')
-                self.message_append_note(cr, uid, [procurement.id], body=message)
+                self.message_post(cr, uid, [procurement.id], body=message)
                 cr.execute('update procurement_order set message=%s where id=%s', (message, procurement.id))
                 return False
             if user.company_id and user.company_id.partner_id:
@@ -306,7 +306,7 @@ class procurement_order(osv.osv):
             address_id = partner_obj.address_get(cr, uid, [partner.id], ['delivery'])['delivery']
             if not address_id:
                 message = _('No address defined for the supplier')
-                self.message_append_note(cr, uid, [procurement.id], body=message)
+                self.message_post(cr, uid, [procurement.id], body=message)
                 cr.execute('update procurement_order set message=%s where id=%s', (message, procurement.id))
                 return False
         return True
@@ -365,7 +365,7 @@ class procurement_order(osv.osv):
         message = _('From stock: products assigned.')
         self.write(cr, uid, ids, {'state': 'running',
                 'message': message}, context=context)
-        self.message_append_note(cr, uid, ids, body=message, context=context)
+        self.message_post(cr, uid, ids, body=message, context=context)
         self.running_send_note(cr, uid, ids, context=context)
         return True
 
@@ -389,7 +389,7 @@ class procurement_order(osv.osv):
                 ok = ok and self.pool.get('stock.move').action_assign(cr, uid, [id])
                 order_point_id = self.pool.get('stock.warehouse.orderpoint').search(cr, uid, [('product_id', '=', procurement.product_id.id)], context=context)
                 if not order_point_id and not ok:
-                     message = _("Not enough stock and no minimum orderpoint rule defined.")
+                    message = _("Not enough stock and no minimum orderpoint rule defined.")
                 elif not order_point_id:
                     message = _("No minimum orderpoint rule defined.")
                 elif not ok:
@@ -398,7 +398,7 @@ class procurement_order(osv.osv):
                 if message:
                     message = _("Procurement '%s' is in exception: ") % (procurement.name) + message
                     cr.execute('update procurement_order set message=%s where id=%s', (message, procurement.id))
-                    self.message_append_note(cr, uid, [procurement.id], body=message, context=context)
+                    self.message_post(cr, uid, [procurement.id], body=message, context=context)
         return ok
 
     def action_produce_assign_service(self, cr, uid, ids, context=None):
@@ -496,22 +496,22 @@ class procurement_order(osv.osv):
         return obj_id
 
     def create_send_note(self, cr, uid, ids, context=None):
-        self.message_append_note(cr, uid, ids, body=_("Procurement has been <b>created</b>."), context=context)
+        self.message_post(cr, uid, ids, body=_("Procurement has been <b>created</b>."), context=context)
 
     def confirm_send_note(self, cr, uid, ids, context=None):
-        self.message_append_note(cr, uid, ids, body=_("Procurement has been <b>confirmed</b>."), context=context)
+        self.message_post(cr, uid, ids, body=_("Procurement has been <b>confirmed</b>."), context=context)
 
     def running_send_note(self, cr, uid, ids, context=None):
-        self.message_append_note(cr, uid, ids, body=_("Procurement has been set to <b>running</b>."), context=context)
+        self.message_post(cr, uid, ids, body=_("Procurement has been set to <b>running</b>."), context=context)
 
     def ready_send_note(self, cr, uid, ids, context=None):
-        self.message_append_note(cr, uid, ids, body=_("Procurement has been set to <b>ready</b>."), context=context)
+        self.message_post(cr, uid, ids, body=_("Procurement has been set to <b>ready</b>."), context=context)
 
     def cancel_send_note(self, cr, uid, ids, context=None):
-        self.message_append_note(cr, uid, ids, body=_("Procurement has been <b>cancelled</b>."), context=context)
+        self.message_post(cr, uid, ids, body=_("Procurement has been <b>cancelled</b>."), context=context)
 
     def done_send_note(self, cr, uid, ids, context=None):
-        self.message_append_note(cr, uid, ids, body=_("Procurement has been <b>done</b>."), context=context)
+        self.message_post(cr, uid, ids, body=_("Procurement has been <b>done</b>."), context=context)
 
 procurement_order()
 
index 7a87697..1c8ac59 100644 (file)
@@ -54,7 +54,6 @@ class procurement_order(osv.osv):
         '''
         if context is None:
             context = {}
-
         try:
             if use_new_cursor:
                 cr = pooler.get_db(use_new_cursor).cursor()
@@ -119,19 +118,6 @@ class procurement_order(osv.osv):
                 offset += len(ids)
                 if not ids: break
             end_date = fields.datetime.now()
-            if uid:
-                # Chatter: old res.request is now a chatter on res.users, id=uid
-                summary = _("""Here is the procurement scheduling report.
-
-        Start Time: %s 
-        End Time: %s 
-        Total Procurements processed: %d 
-        Procurements with exceptions: %d 
-        Skipped Procurements (scheduled date outside of scheduler range) %d 
-
-        Exceptions:\n""") % (start_date, end_date, report_total, report_except, report_later)
-                summary += '\n'.join(report)
-                procurement_obj.message_append_note(cr, uid, ids, body=summary, context=context)
 
             if use_new_cursor:
                 cr.commit()
index 1b8c69b..16b08e6 100644 (file)
@@ -580,7 +580,7 @@ class product_product(osv.osv):
         return obj_id
 
     def create_send_note(self, cr, uid, ids, context=None):
-        return self.message_append_note(cr, uid, ids, body=_("Product has been <b>created</b>."), context=context)
+        return self.message_post(cr, uid, ids, body=_("Product has been <b>created</b>."), context=context)
 
     def unlink(self, cr, uid, ids, context=None):
         unlink_ids = []
index f16cb90..241e3a2 100644 (file)
@@ -67,7 +67,7 @@ class project(osv.osv):
     _description = "Project"
     _inherits = {'account.analytic.account': "analytic_account_id",
                  "mail.alias": "alias_id"}
-    _inherit = ['ir.needaction_mixin', 'mail.thread']
+    _inherit = ['mail.thread', 'ir.needaction_mixin']
 
     def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
         if user == 1:
@@ -187,23 +187,6 @@ class project(osv.osv):
         """Overriden in project_issue to offer more options"""
         return [('project.task', "Tasks")]
 
-    def _get_followers(self, cr, uid, ids, name, arg, context=None):
-        '''
-        Functional field that computes the users that are 'following' a thread.
-        '''
-        res = {}
-        for project in self.browse(cr, uid, ids, context=context):
-            l = set()
-            for message in project.message_ids:
-                l.add(message.user_id and message.user_id.id or False)
-            res[project.id] = list(filter(None, l))
-        return res
-
-    def _search_followers(self, cr, uid, obj, name, args, context=None):
-        project_obj = self.pool.get('project.project')
-        project_ids = project_obj.search(cr, uid, [('message_ids.user_id.id', 'in', args[0][2])], context=context)
-        return [('id', 'in', project_ids)]
-
     # Lambda indirection method to avoid passing a copy of the overridable method when declaring the field
     _alias_models = lambda self, *args, **kwargs: self._get_alias_models(*args, **kwargs)
 
@@ -247,8 +230,6 @@ class project(osv.osv):
                                         help="The kind of document created when an email is received on this project's email alias"),
         'privacy_visibility': fields.selection([('public','Public'), ('followers','Followers Only')], 'Privacy / Visibility', required=True),
         'state': fields.selection([('template', 'Template'),('draft','New'),('open','In Progress'), ('cancelled', 'Cancelled'),('pending','Pending'),('close','Closed')], 'Status', required=True,),
-        'followers': fields.function(_get_followers, method=True, fnct_search=_search_followers,
-                        type='many2many', relation='res.users', string='Followers'),
      }
 
     def _get_type_common(self, cr, uid, context):
@@ -512,11 +493,6 @@ def Project():
     # OpenChatter methods and notifications
     # ------------------------------------------------
 
-    def message_get_monitored_follower_fields(self, cr, uid, ids, context=None):
-        """ Add 'user_id' to the monitored fields """
-        res = super(project, self).message_get_monitored_follower_fields(cr, uid, ids, context=context)
-        return res + ['user_id']
-
     def create(self, cr, uid, vals, context=None):
         if context is None: context = {}
         # Prevent double project creation when 'use_tasks' is checked!
@@ -537,23 +513,23 @@ def Project():
         return project_id
 
     def create_send_note(self, cr, uid, ids, context=None):
-        return self.message_append_note(cr, uid, ids, body=_("Project has been <b>created</b>."), context=context)
+        return self.message_post(cr, uid, ids, body=_("Project has been <b>created</b>."), context=context)
 
     def set_open_send_note(self, cr, uid, ids, context=None):
         message = _("Project has been <b>opened</b>.")
-        return self.message_append_note(cr, uid, ids, body=message, context=context)
+        return self.message_post(cr, uid, ids, body=message, context=context)
 
     def set_pending_send_note(self, cr, uid, ids, context=None):
         message = _("Project is now <b>pending</b>.")
-        return self.message_append_note(cr, uid, ids, body=message, context=context)
+        return self.message_post(cr, uid, ids, body=message, context=context)
 
     def set_cancel_send_note(self, cr, uid, ids, context=None):
         message = _("Project has been <b>cancelled</b>.")
-        return self.message_append_note(cr, uid, ids, body=message, context=context)
+        return self.message_post(cr, uid, ids, body=message, context=context)
 
     def set_close_send_note(self, cr, uid, ids, context=None):
         message = _("Project has been <b>closed</b>.")
-        return self.message_append_note(cr, uid, ids, body=message, context=context)
+        return self.message_post(cr, uid, ids, body=message, context=context)
 
     def write(self, cr, uid, ids, vals, context=None):
         # if alias_model has been changed, update alias_model_id accordingly
@@ -566,7 +542,7 @@ class task(base_stage, osv.osv):
     _name = "project.task"
     _description = "Task"
     _date_name = "date_start"
-    _inherit = ['ir.needaction_mixin', 'mail.thread']
+    _inherit = ['mail.thread', 'ir.needaction_mixin']
 
     def _get_default_project_id(self, cr, uid, context=None):
         """ Gives default section by checking if present in the context """
@@ -1180,6 +1156,45 @@ class task(base_stage, osv.osv):
         return result
 
     # ---------------------------------------------------
+    # mail gateway
+    # ---------------------------------------------------
+
+    def message_new(self, cr, uid, msg, custom_values=None, context=None):
+        """ Override to updates the document according to the email. """
+        if custom_values is None: custom_values = {}
+        custom_values.update({
+            'name': subject,
+            'planned_hours': 0.0,
+            'subject': msg.get('subject'),
+        })
+        return super(project_tasks,self).message_new(cr, uid, msg, custom_values=custom_values, context=context)
+
+    def message_update(self, cr, uid, ids, msg, update_vals=None, context=None):
+        """ Override to update the task according to the email. """
+        if update_vals is None: update_vals = {}
+        act = False
+        maps = {
+            'cost':'planned_hours',
+        }
+        for line in msg['body'].split('\n'):
+            line = line.strip()
+            res = tools.misc.command_re.match(line)
+            if res:
+                match = res.group(1).lower()
+                field = maps.get(match)
+                if field:
+                    try:
+                        update_vals[field] = float(res.group(2).lower())
+                    except (ValueError, TypeError):
+                        pass
+                elif match.lower() == 'state' \
+                        and res.group(2).lower() in ['cancel','close','draft','open','pending']:
+                    act = 'do_%s' % res.group(2).lower()
+        if act:
+            getattr(self,act)(cr, uid, ids, context=context)
+        return super(project_tasks,self).message_update(cr, uid, msg, update_vals=update_vals, context=context)
+
+    # ---------------------------------------------------
     # OpenChatter methods and notifications
     # ---------------------------------------------------
 
@@ -1207,19 +1222,19 @@ class task(base_stage, osv.osv):
     def stage_set_send_note(self, cr, uid, ids, stage_id, context=None):
         """ Override of the (void) default notification method. """
         stage_name = self.pool.get('project.task.type').name_get(cr, uid, [stage_id], context=context)[0][1]
-        return self.message_append_note(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context)
+        return self.message_post(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context)
 
     def create_send_note(self, cr, uid, ids, context=None):
-        return self.message_append_note(cr, uid, ids, body=_("Task has been <b>created</b>."), context=context)
+        return self.message_post(cr, uid, ids, body=_("Task has been <b>created</b>."), context=context)
 
     def case_draft_send_note(self, cr, uid, ids, context=None):
         msg = _('Task has been set as <b>draft</b>.')
-        return self.message_append_note(cr, uid, ids, body=msg, context=context)
+        return self.message_post(cr, uid, ids, body=msg, context=context)
 
     def do_delegation_send_note(self, cr, uid, ids, context=None):
         for task in self.browse(cr, uid, ids, context=context):
             msg = _('Task has been <b>delegated</b> to <em>%s</em>.') % (task.user_id.name)
-            self.message_append_note(cr, uid, [task.id], body=msg, context=context)
+            self.message_post(cr, uid, [task.id], body=msg, context=context)
         return True
 
 
index c2d37f7..5cc4c99 100644 (file)
         </record>
 
         <!-- notify all employees of module installation -->
-        <function model="mail.group" name="message_append_note">
-            <!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
-            <value eval="[ref('mail.group_all_employees')]"/>
-            <value>Module Project Management has been installed</value>
-            <value>Manage multi-level projects and tasks.  You can delegate tasks, track the work done on tasks, and review your planning based on the entered data.
+        <record model="mail.message" id="module_install_notification">
+            <field name="model">mail.group</field>
+            <field name="res_id" ref="mail.group_all_employees"/>
+            <field name="type">notification</field>
+            <field name="subject">Project Management application installed!</field>
+            <field name="body">Manage multi-level projects and tasks.  You can delegate tasks, track task work, and review your planning.
 
-You can manage todo lists on tasks by installing the module "Todo Lists", which supports the methodology Getting Things Done (GTD).  You can also manage issues/bugs in projects by installing the module "Issues Tracker."</value>
-        </function>
+You can manage todo lists on tasks by installing the "Todo Lists" application, supporting the Getting Things Done (GTD) methodology.
+You can also manage issues/bugs in projects by installing the "Issue Tracker" application.</field>
+        </record>
     </data>
 </openerp>
index 0f0af01..cd22bad 100644 (file)
         <record id="message_task_1" model="mail.message">
             <field name="model">project.task</field>
             <field name="res_id" ref="project_task_22"/>
-            <field name="content_subtype">plain</field>
-            <field name="body_text">Hello Quentin,
+            <field name="body">Hello Demo,
 There is a change in customer requirement.
 Can you check the document from customer again.
 Thanks,</field>
             <field name="type">comment</field>
-            <field name="user_id" ref="base.user_root"/>
+            <field name="author_id" ref="base.partner_root"/>
         </record>
-
         <record id="message_task_2" model="mail.message">
             <field name="model">project.task</field>
             <field name="res_id" ref="project_task_22"/>
             <field name="parent_id" ref="message_task_1"/>
-            <field name="content_subtype">plain</field>
-            <field name="body_text">Ok, I have checked the mail,
+            <field name="body">Ok, I have checked the mail,
 I will update the document and let you know.</field>
             <field name="type">comment</field>
-            <field name="user_id" ref="base.user_demo"/>
+            <field name="author_id" ref="base.partner_demo"/>
         </record>
-
-
         <record id="message_task_3" model="mail.message">
             <field name="model">project.task</field>
             <field name="res_id" ref="project_task_22"/>
             <field name="parent_id" ref="message_task_2"/>
-            <field name="content_subtype">plain</field>
-            <field name="body_text">Fine!
+            <field name="body">Fine!
 Send it ASAP, its urgent.</field>
             <field name="type">comment</field>
-            <field name="user_id" ref="base.user_root"/>
+            <field name="author_id" ref="base.partner_root"/>
         </record>
 
     </data>
index a204669..d359cdb 100644 (file)
             <field name="arch" type="xml">
                 <search string="Search Project">
                     <field name="complete_name" string="Project Name"/>
-                    <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="needaction_pending" domain="[('needaction_pending','=',True)]"/>
+                    <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/>
                     <separator/>
                     <filter icon="terp-check" string="Open" name="Current" domain="[('state', '=','open')]" help="Open Projects"/>
                     <filter icon="gtk-media-pause" string="Pending" name="Pending" domain="[('state', '=','pending')]" help="Pending Projects"/>
             <field name="model">project.project</field>
             <field name="field_parent">child_ids</field>
             <field name="arch" type="xml">
-                <tree fonts="bold:needaction_pending==True" colors="red:date and (date&lt;current_date) and (state == 'open');blue:state in ('draft','pending');grey: state in ('close','cancelled')" string="Projects">
+                <tree fonts="bold:message_unread==True" colors="red:date and (date&lt;current_date) and (state == 'open');blue:state in ('draft','pending');grey: state in ('close','cancelled')" string="Projects">
                     <field name="sequence" invisible="1"/>
-                    <field name="needaction_pending" invisible="1"/>
+                    <field name="message_unread" invisible="1"/>
                     <field name="date" invisible="1"/>
                     <field name="name" string="Project Name"/>
                     <field name="user_id" string="Project Manager"/>
                     <field name="remaining_hours" sum="Remaining Time" groups="project.group_time_work_estimation_tasks"/>
                     <field name="date_deadline"/>
                     <field name="message_summary"/>
-                    <field name="needaction_pending"/>
+                    <field name="message_unread"/>
                     <field name="categ_ids"/>
                     <templates>
                     <t t-name="kanban-box">
             <field name="model">project.task</field>
             <field eval="2" name="priority"/>
             <field name="arch" type="xml">
-                <tree fonts="bold:needaction_pending==True" colors="grey:state in ('cancelled','done');blue:state == 'pending';red:date_deadline and (date_deadline&lt;current_date) and (state in ('draft','pending','open'))" string="Tasks">
-                    <field name="needaction_pending" invisible="1"/>
+                <tree fonts="bold:message_unread==True" colors="grey:state in ('cancelled','done');blue:state == 'pending';red:date_deadline and (date_deadline&lt;current_date) and (state in ('draft','pending','open'))" string="Tasks">
+                    <field name="message_unread" invisible="1"/>
                     <field name="sequence" invisible="not context.get('seq_visible', False)"/>
                     <field name="name"/>
                     <field name="project_id" icon="gtk-indent" invisible="context.get('user_invisible', False)"/>
             <field name="arch" type="xml">
                <search string="Tasks">
                     <field name="name" string="Tasks"/>
-                    <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="needaction_pending" domain="[('needaction_pending','=',True)]"/>
+                    <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/>
                     <separator/>
                     <filter name="draft" string="New" domain="[('state','=','draft')]" help="New Tasks" icon="terp-check"/>
                     <filter name="open" string="In Progress" domain="[('state','=','open')]" help="In Progress Tasks" icon="terp-camera_test"/>
index f867285..3afe0f1 100644 (file)
@@ -45,7 +45,7 @@
         <field name="name">public Members</field>
         <field name="model_id" ref="model_project_project"/>
         <field name="global" eval="True"/>
-        <field name="domain_force">['|','|',('privacy_visibility','in',[False,'public']),('members','in',[user.id]),('followers','in',[user.id])]</field>
+        <field name="domain_force">['|','|',('privacy_visibility','in',[False,'public']),('members','in',[user.id]),('follower_ids','in',[user.partner_id.id])]</field>
     </record>
 
     <record model="ir.rule" id="task_comp_rule">
index dc1df93..61b2468 100644 (file)
 
     <data noupdate="1">
         <!-- notify all employees of module installation -->
-        <function model="mail.group" name="message_append_note">
-            <!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
-            <value eval="[ref('mail.group_all_employees')]"/>
-            <value>Module Todo Lists has been installed</value>
-            <value>Add todo items on project tasks, to help you organize your work.  This module supports the methodology Getting Things Done (GTD), created by David Allen, and described in the book of the same name.</value>
-        </function>
+        <record model="mail.message" id="module_install_notification">
+            <field name="model">mail.group</field>
+            <field name="res_id" ref="mail.group_all_employees"/>
+            <field name="type">notification</field>
+            <field name="subject">Todo Lists application installed!</field>
+            <field name="body">Add todo items on project tasks, to help you organize your work.
+This application supports the Getting Things Done (GTD) methodology, based on David Allen's book.</field>
+        </record>
+
     </data>
 </openerp>
index 07e453a..3d005f3 100644 (file)
 ##############################################################################
 
 {
-    'name': 'Issues Tracker',
+    'name': 'Issue Tracker',
     'version': '1.0',
     'category': 'Project Management',
     'sequence': 22,
-    'summary': 'Support, Bug Traker, Helpdesk',
+    'summary': 'Support, Bug Tracker, Helpdesk',
     'description': """
 This module provides Issues/Bugs Management in Project.
 =======================================================
index c2ff76d..22384a1 100644 (file)
@@ -46,8 +46,7 @@ class project_issue(base_stage, osv.osv):
     _name = "project.issue"
     _description = "Project Issue"
     _order = "priority, create_date desc"
-    _inherit = ['ir.needaction_mixin', 'mail.thread']
-    _mail_compose_message = True
+    _inherit = ['mail.thread', 'ir.needaction_mixin']
 
     def _get_default_project_id(self, cr, uid, context=None):
         """ Gives default project by checking if present in the context """
@@ -364,7 +363,6 @@ class project_issue(base_stage, osv.osv):
         return super(project_issue, self).write(cr, uid, ids, vals, context)
 
     def onchange_task_id(self, cr, uid, ids, task_id, context=None):
-        result = {}
         if not task_id:
             return {'value':{}}
         task = self.pool.get('project.task').browse(cr, uid, task_id, context=context)
@@ -454,14 +452,13 @@ class project_issue(base_stage, osv.osv):
 
         custom_values.update({
             'name':  msg.get('subject') or _("No Subject"),
-            'description': msg.get('body_text'),
+            'description': msg.get('body'),
             'email_from': msg.get('from'),
             'email_cc': msg.get('cc'),
             'user_id': False,
         })
         if  msg.get('priority'):
             custom_values['priority'] =  msg.get('priority')
-        custom_values.update(self.message_partner_by_email(cr, uid, msg.get('from'), context=context))
 
         res_id = super(project_issue, self).message_new(cr, uid, msg, custom_values=custom_values, context=context)
         # self.convert_to_bug(cr, uid, [res_id], context=context)
@@ -477,16 +474,16 @@ class project_issue(base_stage, osv.osv):
         if update_vals is None: update_vals = {}
 
         # Update doc values according to the message
-        update_vals['description'] = msg.get('body_text', '')
+        update_vals['description'] = msg.get('body', '')
         if msg.get('priority'):
             update_vals['priority'] = msg.get('priority')
-        # Parse 'body_text' to find values to update
+        # Parse 'body' to find values to update
         maps = {
             'cost': 'planned_cost',
             'revenue': 'planned_revenue',
             'probability': 'probability',
         }
-        for line in msg.get('body_text', '').split('\n'):
+        for line in msg.get('body', '').split('\n'):
             line = line.strip()
             res = tools.misc.command_re.match(line)
             if res and maps.get(res.group(1).lower(), False):
@@ -499,36 +496,31 @@ class project_issue(base_stage, osv.osv):
     # OpenChatter methods and notifications
     # -------------------------------------------------------
 
-    def message_get_monitored_follower_fields(self, cr, uid, ids, context=None):
-        """ Add 'user_id' to the monitored fields """
-        res = super(project_issue, self).message_get_monitored_follower_fields(cr, uid, ids, context=context)
-        return res + ['user_id']
-
     def stage_set_send_note(self, cr, uid, ids, stage_id, context=None):
         """ Override of the (void) default notification method. """
         stage_name = self.pool.get('project.task.type').name_get(cr, uid, [stage_id], context=context)[0][1]
-        return self.message_append_note(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context)
+        return self.message_post(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context)
 
     def case_get_note_msg_prefix(self, cr, uid, id, context=None):
         """ Override of default prefix for notifications. """
         return 'Project issue'
 
     def convert_to_task_send_note(self, cr, uid, ids, context=None):
-        message = _("Project issue has been <b>converted</b> into task.")
-        return self.message_append_note(cr, uid, ids, body=message, context=context)
+        message = _("Project issue <b>converted</b> to task.")
+        return self.message_post(cr, uid, ids, body=message, context=context)
 
     def create_send_note(self, cr, uid, ids, context=None):
-        message = _("Project issue has been <b>created</b>.")
-        return self.message_append_note(cr, uid, ids, body=message, context=context)
+        message = _("Project issue <b>created</b>.")
+        return self.message_post(cr, uid, ids, body=message, context=context)
 
     def case_escalate_send_note(self, cr, uid, ids, context=None):
         for obj in self.browse(cr, uid, ids, context=context):
             if obj.project_id:
-                message = _("has been <b>escalated</b> to <em>'%s'</em>.") % (obj.project_id.name)
-                obj.message_append_note(body=message, context=context)
+                message = _("<b>escalated</b> to <em>'%s'</em>.") % (obj.project_id.name)
+                obj.message_post(body=message)
             else:
-                message = _("has been <b>escalated</b>.")
-                obj.message_append_note(body=message, context=context)
+                message = _("<b>escalated</b>.")
+                obj.message_post(body=message)
         return True
 
 project_issue()
index a191cb4..ddb0d57 100644 (file)
         </record>
 
         <!-- notify all employees of module installation -->
-        <function model="mail.group" name="message_append_note">
-            <!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
-            <value eval="[ref('mail.group_all_employees')]"/>
-            <value>Module Issues Tracker has been installed.</value>
-            <value>Manage the issues you might face in a project, like bugs in a system, client complaints or material breakdowns.  You can record issues, assign them to some responsible person, and keep track of their status as they evolve over time.
-
-You can access issues from the top menu Project, and access the issues of a specific project from the projects gallery view.</value>
-        </function>
+        <record model="mail.message" id="module_install_notification">
+            <field name="model">mail.group</field>
+            <field name="res_id" ref="mail.group_all_employees"/>
+            <field name="type">notification</field>
+            <field name="subject">Issue Tracker application installed!</field>
+            <field name="body">Manage the issues you might face in a project, such as bugs in a system, client complaints or material breakdowns.
+You can record issues, assign them to a responsible person, and keep track of their status as they evolve over time.
+Access all issues from the top Project menu, and access the issues of a specific project via the projects gallery view.</field>
+        </record>
+
     </data>
 </openerp>
index 564060e..5096779 100644 (file)
             <field name="name">Project Issue Tracker Tree</field>
             <field name="model">project.issue</field>
             <field name="arch" type="xml">
-                <tree string="Issue Tracker Tree" fonts="bold:needaction_pending==True" colors="black:state=='open';blue:state=='pending';grey:state in ('cancel', 'done')">
-                    <field name="needaction_pending" invisible="1"/>
+                <tree string="Issue Tracker Tree" fonts="bold:message_unread==True" colors="black:state=='open';blue:state=='pending';grey:state in ('cancel', 'done')">
+                    <field name="message_unread" invisible="1"/>
                     <field name="id"/>
                     <field name="create_date" groups="base.group_no_one"/>
                     <field name="name"/>
                 <search string="Issue Tracker Search">
                     <field name="name" string="Issue" filter_domain="['|', '|',('partner_id','ilike',self),('email_from','ilike',self),('name','ilike',self)]"/>
                     <field name="id"/>
-                    <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="needaction_pending" domain="[('needaction_pending','=',True)]"/>
+                    <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/>
                     <separator/>
                     <filter string="New" icon="terp-document-new" domain="[('state','=','draft')]" help="New Issues"/>
                     <filter string="To Do" domain="[('state','=','open')]" help="To Do Issues" icon="terp-check"/>
             <field name="name">Project Issue- Feature Tracker Tree</field>
             <field name="model">project.issue</field>
             <field name="arch" type="xml">
-                <tree string="Feature Tracker Tree" fonts="bold:needaction_pending==True" colors="red:state=='open';black:state in ('draft', 'cancel','done','pending')">
+                <tree string="Feature Tracker Tree" fonts="bold:message_unread==True" colors="red:state=='open';black:state in ('draft', 'cancel','done','pending')">
                     <field name="id"/>
-                    <field name="needaction_pending" invisible="1"/>
+                    <field name="message_unread" invisible="1"/>
                     <field name="name" string="Feature description"/>
                     <field name="partner_id"/>
                     <field name="priority" string="Priority"/>
diff --git a/addons/project_mailgate/__init__.py b/addons/project_mailgate/__init__.py
deleted file mode 100644 (file)
index 0f82ebd..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-#    OpenERP, Open Source Management Solution
-#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
-#
-#    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 project_mailgate
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/project_mailgate/__openerp__.py b/addons/project_mailgate/__openerp__.py
deleted file mode 100644 (file)
index 05eacb7..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-#    OpenERP, Open Source Management Solution
-#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
-#
-#    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/>.
-#
-##############################################################################
-
-
-{
-    'name': 'Tasks-Mail Integration',
-    'version': '1.1',
-    'author': 'OpenERP SA',
-    'website': 'http://www.openerp.com',
-    'category': 'Project Management',
-    'images': ['images/project_mailgate_task.jpeg'],
-    'depends': ['project', 'mail'],
-    'description': """
-This module can automatically create Project Tasks based on incoming emails.
-============================================================================
-
-Allows creating tasks based on new emails arriving at a given mailbox,
-similarly to what the CRM application has for Leads/Opportunities.
-
-There are two common alternatives to configure the mailbox integration:
------------------------------------------------------------------------
-    * Install the ``fetchmail`` module and configure a new mailbox, then select
-      ``Project Tasks`` as the target for incoming emails.
-    * Set it up manually on your mail server based on the 'mail gateway' script
-      provided in the ``mail`` module - and connect it to the `project.task` model.
-    """,
-    'data': [],
-    'demo': [],
-    'installable': True,
-    'auto_install': False,
-    'certificate': '001075048780413258261',
-}
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/project_mailgate/i18n/ar.po b/addons/project_mailgate/i18n/ar.po
deleted file mode 100644 (file)
index 42b0945..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-# Arabic translation for openobject-addons
-# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
-"POT-Creation-Date: 2012-02-08 00:37+0000\n"
-"PO-Revision-Date: 2012-01-12 21:12+0000\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: Arabic <ar@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-08-28 06:40+0000\n"
-"X-Generator: Launchpad (build 15864)\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr "معلومات المحفوظات"
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr "مهمة"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr "خطأ ! لا يمكنك انشاء مهام رجعية."
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr "الرسائل"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:90
-#, python-format
-msgid "Draft"
-msgstr "مسودة"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! Task end-date must be greater then task start-date"
-msgstr "خطأ! يجب ان يكون تاريخ انتهاء المهمة اكبر من تاريخ البداية"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:116
-#, python-format
-msgid "Cancel"
-msgstr "إلغاء"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:110
-#, python-format
-msgid "Done"
-msgstr "تم"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:96
-#, python-format
-msgid "Open"
-msgstr "فتح"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:102
-#, python-format
-msgid "Pending"
-msgstr "معلّق"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr "محفوظات"
-
-#~ msgid "Attachments"
-#~ msgstr "مرفقات"
-
-#~ msgid "Details"
-#~ msgstr "تفاصيل"
-
-#~ msgid "Project MailGateWay"
-#~ msgstr "مدخل بريد المشروع"
-
-#~ msgid ""
-#~ "This module is an interface that synchronises mails with OpenERP Project "
-#~ "Task.\n"
-#~ "\n"
-#~ "It allows creating tasks as soon as a new mail arrives in our configured "
-#~ "mail server.\n"
-#~ "Moreover, it keeps track of all further communications and task states.\n"
-#~ "    "
-#~ msgstr ""
-#~ "هذه الوحدة هي تفاعل التي تتزامن البريد مع مهام مشروع OpenERP .\n"
-#~ "وهي تسمح بإنشاء المهام في اقرب وقت كما يصل البريد الجديد في خادم البريد الذي "
-#~ "تم ضبطه.\n"
-#~ "علاوة على ذلك, تستمر في تعقب المزيد من كل الاتصالات وحالات المهمة.\n"
-#~ "    "
diff --git a/addons/project_mailgate/i18n/ca.po b/addons/project_mailgate/i18n/ca.po
deleted file mode 100644 (file)
index 622d0a5..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-# Catalan translation for openobject-addons
-# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
-"POT-Creation-Date: 2012-02-08 00:37+0000\n"
-"PO-Revision-Date: 2011-02-06 21:57+0000\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: Catalan <ca@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-08-28 06:40+0000\n"
-"X-Generator: Launchpad (build 15864)\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr "Informació històrica"
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr "Tasca"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr "Error! No podeu crear tasques recursives."
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr "Missatges"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:90
-#, python-format
-msgid "Draft"
-msgstr "Esborrany"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! Task end-date must be greater then task start-date"
-msgstr ""
-"Error ! La data final de la tasca ha de ser major que la data d'inici"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:116
-#, python-format
-msgid "Cancel"
-msgstr "Canceŀla"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:110
-#, python-format
-msgid "Done"
-msgstr "Realitzat"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:96
-#, python-format
-msgid "Open"
-msgstr "Obre"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:102
-#, python-format
-msgid "Pending"
-msgstr "Pendent"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr "Històric"
-
-#~ msgid "Project MailGateWay"
-#~ msgstr "Ruta d'enllaç del projecte"
-
-#~ msgid "Attachments"
-#~ msgstr "Adjunts"
-
-#~ msgid "Details"
-#~ msgstr "Detalls"
-
-#~ msgid ""
-#~ "This module is an interface that synchronises mails with OpenERP Project "
-#~ "Task.\n"
-#~ "\n"
-#~ "It allows creating tasks as soon as a new mail arrives in our configured "
-#~ "mail server.\n"
-#~ "Moreover, it keeps track of all further communications and task states.\n"
-#~ "    "
-#~ msgstr ""
-#~ "Aquest mòdul proporciona una interfície per sincronitzar correus amb les "
-#~ "tasques de projectes d'OpenERP.\n"
-#~ "\n"
-#~ "Permet crear tasques tan aviat com arriba un nou correu en el nostre "
-#~ "servidor de correu prèviament configurat.\n"
-#~ "A més realitza un seguiment de totes les comunicacions addicionals i estats "
-#~ "de la tasca.\n"
-#~ "    "
diff --git a/addons/project_mailgate/i18n/cs.po b/addons/project_mailgate/i18n/cs.po
deleted file mode 100644 (file)
index b65ae79..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-# Czech translation for openobject-addons
-# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
-"POT-Creation-Date: 2012-02-08 00:37+0000\n"
-"PO-Revision-Date: 2012-04-06 06:41+0000\n"
-"Last-Translator: Jiří Hajda <robie@centrum.cz>\n"
-"Language-Team: Czech <cs@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-08-28 06:40+0000\n"
-"X-Generator: Launchpad (build 15864)\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr "Informace historie"
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr "Úkol"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr "Chyba ! Nemůžete vytvořit rekurzivní úkoly."
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr "Zprávy"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:90
-#, python-format
-msgid "Draft"
-msgstr "Koncept"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! Task end-date must be greater then task start-date"
-msgstr "Chyba ! Datum ukončení úkolu musí být větší než počáteční datum"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:116
-#, python-format
-msgid "Cancel"
-msgstr "Zrušit"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:110
-#, python-format
-msgid "Done"
-msgstr "Dokončené"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:96
-#, python-format
-msgid "Open"
-msgstr "Otevřené"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:102
-#, python-format
-msgid "Pending"
-msgstr "Čekající"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr "Historie"
diff --git a/addons/project_mailgate/i18n/da.po b/addons/project_mailgate/i18n/da.po
deleted file mode 100644 (file)
index 43aeba4..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-# Danish translation for openobject-addons
-# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
-"POT-Creation-Date: 2012-02-08 00:37+0000\n"
-"PO-Revision-Date: 2012-01-27 06:33+0000\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: Danish <da@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-08-28 06:40+0000\n"
-"X-Generator: Launchpad (build 15864)\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr ""
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr ""
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr ""
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr ""
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:90
-#, python-format
-msgid "Draft"
-msgstr ""
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! Task end-date must be greater then task start-date"
-msgstr ""
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:116
-#, python-format
-msgid "Cancel"
-msgstr ""
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:110
-#, python-format
-msgid "Done"
-msgstr ""
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:96
-#, python-format
-msgid "Open"
-msgstr ""
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:102
-#, python-format
-msgid "Pending"
-msgstr ""
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr ""
diff --git a/addons/project_mailgate/i18n/de.po b/addons/project_mailgate/i18n/de.po
deleted file mode 100644 (file)
index a8f8bfa..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-# German translation for openobject-addons
-# Copyright (c) 2010 Rosetta Contributors and Canonical Ltd 2010
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2010.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
-"POT-Creation-Date: 2012-02-08 00:37+0000\n"
-"PO-Revision-Date: 2010-12-31 10:37+0000\n"
-"Last-Translator: Thorsten Vocks (OpenBig.org) <thorsten.vocks@big-"
-"consulting.net>\n"
-"Language-Team: German <de@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-08-28 06:40+0000\n"
-"X-Generator: Launchpad (build 15864)\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr "Information Historie"
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr "Aufgabe"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr "Fehler ! Sie können keine rekursiven Aufgaben definieren."
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr "Nachrichten"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:90
-#, python-format
-msgid "Draft"
-msgstr "Entwurf"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! Task end-date must be greater then task start-date"
-msgstr "Fehler! Aufgaben End-Datum muss größer als Aufgaben-Beginn sein"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:116
-#, python-format
-msgid "Cancel"
-msgstr "Abbrechen"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:110
-#, python-format
-msgid "Done"
-msgstr "Erledigt"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:96
-#, python-format
-msgid "Open"
-msgstr "Offen"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:102
-#, python-format
-msgid "Pending"
-msgstr "Im Wartezustand"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr "Historie"
-
-#~ msgid "Project MailGateWay"
-#~ msgstr "Projekte Mailgateway"
-
-#~ msgid ""
-#~ "This module is an interface that synchronises mails with OpenERP Project "
-#~ "Task.\n"
-#~ "\n"
-#~ "It allows creating tasks as soon as a new mail arrives in our configured "
-#~ "mail server.\n"
-#~ "Moreover, it keeps track of all further communications and task states.\n"
-#~ "    "
-#~ msgstr ""
-#~ "Dieses Modul ermöglicht die Synchronisation von Email mit openERP "
-#~ "Projektaufgaben.\n"
-#~ "\n"
-#~ "Es ermöglicht die Erstellung einer neuen Aufgabe sobald eine neue EMail von "
-#~ "Ihrem Mailserver empfangen wird.\n"
-#~ "Ausserdem können Sie dann alle weitere Korrespondenz und Kommunikation "
-#~ "rückverfolgen sowie den Status der Aufgabe überwachen.\n"
-#~ "    "
-
-#~ msgid "Details"
-#~ msgstr "Details"
-
-#~ msgid "Attachments"
-#~ msgstr "Anhänge"
diff --git a/addons/project_mailgate/i18n/es.po b/addons/project_mailgate/i18n/es.po
deleted file mode 100644 (file)
index e5aab3c..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-# Spanish translation for openobject-addons
-# Copyright (c) 2010 Rosetta Contributors and Canonical Ltd 2010
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2010.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
-"POT-Creation-Date: 2012-02-08 00:37+0000\n"
-"PO-Revision-Date: 2010-12-28 08:35+0000\n"
-"Last-Translator: Jordi Esteve (www.zikzakmedia.com) "
-"<jesteve@zikzakmedia.com>\n"
-"Language-Team: Spanish <es@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-08-28 06:40+0000\n"
-"X-Generator: Launchpad (build 15864)\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr "Información histórica"
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr "Tarea"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr "¡Error! No puede crear tareas recursivas."
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr "Mensajes"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:90
-#, python-format
-msgid "Draft"
-msgstr "Borrador"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! Task end-date must be greater then task start-date"
-msgstr ""
-"¡ Error ! La fecha final de la tarea debe ser mayor que la fecha de inicio"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:116
-#, python-format
-msgid "Cancel"
-msgstr "Cancelar"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:110
-#, python-format
-msgid "Done"
-msgstr "Realizado"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:96
-#, python-format
-msgid "Open"
-msgstr "Abrir"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:102
-#, python-format
-msgid "Pending"
-msgstr "Pendiente"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr "Historial"
-
-#~ msgid "Project MailGateWay"
-#~ msgstr "Ruta de enlace del proyecto"
-
-#~ msgid "Details"
-#~ msgstr "Detalles"
-
-#~ msgid ""
-#~ "This module is an interface that synchronises mails with OpenERP Project "
-#~ "Task.\n"
-#~ "\n"
-#~ "It allows creating tasks as soon as a new mail arrives in our configured "
-#~ "mail server.\n"
-#~ "Moreover, it keeps track of all further communications and task states.\n"
-#~ "    "
-#~ msgstr ""
-#~ "Este módulo proporciona una interfaz para sincronizar correos con las tareas "
-#~ "de proyectos de OpenERP.\n"
-#~ "Permite crear tareas tan pronto como llega un nuevo correo en nuestro "
-#~ "servidor de correo previamente configurado.\n"
-#~ "Además realiza un seguimiento de todas las comunicaciones adicionales y "
-#~ "estados de la tarea.\n"
-#~ "    "
-
-#~ msgid "Attachments"
-#~ msgstr "Adjuntos"
diff --git a/addons/project_mailgate/i18n/es_CR.po b/addons/project_mailgate/i18n/es_CR.po
deleted file mode 100644 (file)
index 4e420fa..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-# Spanish translation for openobject-addons
-# Copyright (c) 2010 Rosetta Contributors and Canonical Ltd 2010
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2010.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-02-08 00:37+0000\n"
-"PO-Revision-Date: 2012-02-17 00:30+0000\n"
-"Last-Translator: Carlos Vásquez (CLEARCORP) "
-"<carlos.vasquez@clearcorp.co.cr>\n"
-"Language-Team: Spanish <es@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-08-28 06:40+0000\n"
-"X-Generator: Launchpad (build 15864)\n"
-"Language: es\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr "Información histórica"
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr "Tarea"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr "¡Error! No puede crear tareas recursivas."
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr "Mensajes"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:90
-#, python-format
-msgid "Draft"
-msgstr "Borrador"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! Task end-date must be greater then task start-date"
-msgstr ""
-"¡ Error ! La fecha final de la tarea debe ser mayor que la fecha de inicio"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:116
-#, python-format
-msgid "Cancel"
-msgstr "Cancelar"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:110
-#, python-format
-msgid "Done"
-msgstr "Realizado"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:96
-#, python-format
-msgid "Open"
-msgstr "Abrir"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:102
-#, python-format
-msgid "Pending"
-msgstr "Pendiente"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr "Historial"
-
-#~ msgid "Project MailGateWay"
-#~ msgstr "Ruta de enlace del proyecto"
-
-#~ msgid "Details"
-#~ msgstr "Detalles"
-
-#~ msgid ""
-#~ "This module is an interface that synchronises mails with OpenERP Project "
-#~ "Task.\n"
-#~ "\n"
-#~ "It allows creating tasks as soon as a new mail arrives in our configured "
-#~ "mail server.\n"
-#~ "Moreover, it keeps track of all further communications and task states.\n"
-#~ "    "
-#~ msgstr ""
-#~ "Este módulo proporciona una interfaz para sincronizar correos con las tareas "
-#~ "de proyectos de OpenERP.\n"
-#~ "Permite crear tareas tan pronto como llega un nuevo correo en nuestro "
-#~ "servidor de correo previamente configurado.\n"
-#~ "Además realiza un seguimiento de todas las comunicaciones adicionales y "
-#~ "estados de la tarea.\n"
-#~ "    "
-
-#~ msgid "Attachments"
-#~ msgstr "Adjuntos"
diff --git a/addons/project_mailgate/i18n/es_MX.po b/addons/project_mailgate/i18n/es_MX.po
deleted file mode 100644 (file)
index 2736d25..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-# Spanish translation for openobject-addons
-# Copyright (c) 2010 Rosetta Contributors and Canonical Ltd 2010
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2010.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
-"POT-Creation-Date: 2011-01-11 11:15+0000\n"
-"PO-Revision-Date: 2010-12-28 08:35+0000\n"
-"Last-Translator: Jordi Esteve (www.zikzakmedia.com) "
-"<jesteve@zikzakmedia.com>\n"
-"Language-Team: Spanish <es@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2011-09-05 05:56+0000\n"
-"X-Generator: Launchpad (build 13830)\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr "Información histórica"
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr "Tarea"
-
-#. module: project_mailgate
-#: model:ir.module.module,description:project_mailgate.module_meta_information
-msgid ""
-"This module is an interface that synchronises mails with OpenERP Project "
-"Task.\n"
-"\n"
-"It allows creating tasks as soon as a new mail arrives in our configured "
-"mail server.\n"
-"Moreover, it keeps track of all further communications and task states.\n"
-"    "
-msgstr ""
-"Este módulo proporciona una interfaz para sincronizar correos con las tareas "
-"de proyectos de OpenERP.\n"
-"Permite crear tareas tan pronto como llega un nuevo correo en nuestro "
-"servidor de correo previamente configurado.\n"
-"Además realiza un seguimiento de todas las comunicaciones adicionales y "
-"estados de la tarea.\n"
-"    "
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "Attachments"
-msgstr "Adjuntos"
-
-#. module: project_mailgate
-#: model:ir.module.module,shortdesc:project_mailgate.module_meta_information
-msgid "Project MailGateWay"
-msgstr "Ruta de enlace del proyecto"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr "¡Error! No puede crear tareas recursivas."
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr "Mensajes"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:123
-#, python-format
-msgid "Draft"
-msgstr "Borrador"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "Details"
-msgstr "Detalles"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:149
-#, python-format
-msgid "Cancel"
-msgstr "Cancelar"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:143
-#, python-format
-msgid "Done"
-msgstr "Realizado"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:129
-#, python-format
-msgid "Open"
-msgstr "Abrir"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:135
-#, python-format
-msgid "Pending"
-msgstr "Pendiente"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr "Historial"
diff --git a/addons/project_mailgate/i18n/es_VE.po b/addons/project_mailgate/i18n/es_VE.po
deleted file mode 100644 (file)
index 2736d25..0000000
+++ /dev/null
@@ -1,108 +0,0 @@
-# Spanish translation for openobject-addons
-# Copyright (c) 2010 Rosetta Contributors and Canonical Ltd 2010
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2010.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
-"POT-Creation-Date: 2011-01-11 11:15+0000\n"
-"PO-Revision-Date: 2010-12-28 08:35+0000\n"
-"Last-Translator: Jordi Esteve (www.zikzakmedia.com) "
-"<jesteve@zikzakmedia.com>\n"
-"Language-Team: Spanish <es@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2011-09-05 05:56+0000\n"
-"X-Generator: Launchpad (build 13830)\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr "Información histórica"
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr "Tarea"
-
-#. module: project_mailgate
-#: model:ir.module.module,description:project_mailgate.module_meta_information
-msgid ""
-"This module is an interface that synchronises mails with OpenERP Project "
-"Task.\n"
-"\n"
-"It allows creating tasks as soon as a new mail arrives in our configured "
-"mail server.\n"
-"Moreover, it keeps track of all further communications and task states.\n"
-"    "
-msgstr ""
-"Este módulo proporciona una interfaz para sincronizar correos con las tareas "
-"de proyectos de OpenERP.\n"
-"Permite crear tareas tan pronto como llega un nuevo correo en nuestro "
-"servidor de correo previamente configurado.\n"
-"Además realiza un seguimiento de todas las comunicaciones adicionales y "
-"estados de la tarea.\n"
-"    "
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "Attachments"
-msgstr "Adjuntos"
-
-#. module: project_mailgate
-#: model:ir.module.module,shortdesc:project_mailgate.module_meta_information
-msgid "Project MailGateWay"
-msgstr "Ruta de enlace del proyecto"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr "¡Error! No puede crear tareas recursivas."
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr "Mensajes"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:123
-#, python-format
-msgid "Draft"
-msgstr "Borrador"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "Details"
-msgstr "Detalles"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:149
-#, python-format
-msgid "Cancel"
-msgstr "Cancelar"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:143
-#, python-format
-msgid "Done"
-msgstr "Realizado"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:129
-#, python-format
-msgid "Open"
-msgstr "Abrir"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:135
-#, python-format
-msgid "Pending"
-msgstr "Pendiente"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr "Historial"
diff --git a/addons/project_mailgate/i18n/fi.po b/addons/project_mailgate/i18n/fi.po
deleted file mode 100644 (file)
index 76677ae..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-# Finnish translation for openobject-addons
-# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
-"POT-Creation-Date: 2012-02-08 00:37+0000\n"
-"PO-Revision-Date: 2011-06-22 10:01+0000\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: Finnish <fi@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-08-28 06:40+0000\n"
-"X-Generator: Launchpad (build 15864)\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr "Historiatiedot"
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr "Tehtävä"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr "Virhe ! Et voi luoda rekursiivisiä tehtäviä."
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr "Viestit"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:90
-#, python-format
-msgid "Draft"
-msgstr "Luonnos"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! Task end-date must be greater then task start-date"
-msgstr ""
-"Virhe! Tehtävän lopetuspäivän tulee olla myöhäisempi kuin aloituspäivä"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:116
-#, python-format
-msgid "Cancel"
-msgstr "Peruuta"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:110
-#, python-format
-msgid "Done"
-msgstr "Valmis"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:96
-#, python-format
-msgid "Open"
-msgstr "Auki"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:102
-#, python-format
-msgid "Pending"
-msgstr "Odottava"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr "Historia"
-
-#~ msgid "Project MailGateWay"
-#~ msgstr "Projektin sähköpostivälittäjä"
-
-#~ msgid "Attachments"
-#~ msgstr "Liitteet"
-
-#~ msgid "Details"
-#~ msgstr "Yksityiskohdat"
diff --git a/addons/project_mailgate/i18n/fr.po b/addons/project_mailgate/i18n/fr.po
deleted file mode 100644 (file)
index e763eca..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-# French translation for openobject-addons
-# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
-"POT-Creation-Date: 2012-02-08 00:37+0000\n"
-"PO-Revision-Date: 2011-01-13 23:14+0000\n"
-"Last-Translator: lholivier <olivier.lenoir@free.fr>\n"
-"Language-Team: French <fr@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-08-28 06:40+0000\n"
-"X-Generator: Launchpad (build 15864)\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr "Historique"
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr "Tâche"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr "Erreur ! Vous ne pouvez pas créer de tâches récursives."
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr "Messages"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:90
-#, python-format
-msgid "Draft"
-msgstr "Brouillon"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! Task end-date must be greater then task start-date"
-msgstr ""
-"Erreur ! La date de fin de la tâche doit être postérieure à la date de "
-"démarrage"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:116
-#, python-format
-msgid "Cancel"
-msgstr "Annuler"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:110
-#, python-format
-msgid "Done"
-msgstr "Terminé"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:96
-#, python-format
-msgid "Open"
-msgstr "Ouvert"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:102
-#, python-format
-msgid "Pending"
-msgstr "En attente"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr "Historique"
-
-#~ msgid "Attachments"
-#~ msgstr "Pièces jointes"
-
-#~ msgid "Details"
-#~ msgstr "Détails"
-
-#~ msgid "Project MailGateWay"
-#~ msgstr "Messagerie de Projet"
-
-#~ msgid ""
-#~ "This module is an interface that synchronises mails with OpenERP Project "
-#~ "Task.\n"
-#~ "\n"
-#~ "It allows creating tasks as soon as a new mail arrives in our configured "
-#~ "mail server.\n"
-#~ "Moreover, it keeps track of all further communications and task states.\n"
-#~ "    "
-#~ msgstr ""
-#~ "Ce module est un interface de synchronisation des emails avec la gestion de "
-#~ "projet d'OpenERP.\n"
-#~ "\n"
-#~ "Il permet la création de tâches sur l'arrivée de nouveaux emails dans le "
-#~ "serveur de messagerie configuré.\n"
-#~ "De plus, il conserve les échanges ultérieurs et l'état des tâches.\n"
-#~ "    "
diff --git a/addons/project_mailgate/i18n/gl.po b/addons/project_mailgate/i18n/gl.po
deleted file mode 100644 (file)
index 0e7334b..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-# Galician translation for openobject-addons
-# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
-"POT-Creation-Date: 2012-02-08 00:37+0000\n"
-"PO-Revision-Date: 2011-05-10 08:39+0000\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: Galician <gl@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-08-28 06:40+0000\n"
-"X-Generator: Launchpad (build 15864)\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr "Información histórica"
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr "Tarefa"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr "Erro! Non pode crear tarefas recorrentes."
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr "Mensaxes"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:90
-#, python-format
-msgid "Draft"
-msgstr "Borrador"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! Task end-date must be greater then task start-date"
-msgstr ""
-"Erro! A data de remate da tarefa debe ser posterior á data de inicio da "
-"tarefa"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:116
-#, python-format
-msgid "Cancel"
-msgstr "Anular"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:110
-#, python-format
-msgid "Done"
-msgstr "Feito"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:96
-#, python-format
-msgid "Open"
-msgstr "Abrir"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:102
-#, python-format
-msgid "Pending"
-msgstr "Pendente"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr "Historial"
-
-#~ msgid "Project MailGateWay"
-#~ msgstr "Ruta de enlace do proxecto"
-
-#~ msgid ""
-#~ "This module is an interface that synchronises mails with OpenERP Project "
-#~ "Task.\n"
-#~ "\n"
-#~ "It allows creating tasks as soon as a new mail arrives in our configured "
-#~ "mail server.\n"
-#~ "Moreover, it keeps track of all further communications and task states.\n"
-#~ "    "
-#~ msgstr ""
-#~ "Este módulo proporciona unha interface para sincronizar correos coas tarefas "
-#~ "de proxectos de OpenERP. Permite crear tarefas tan pronto como chegue un "
-#~ "novo correo no noso servidor de correo previamente configurado. Ademais "
-#~ "realiza un seguimento de tódalas comunicacións adicionais e dos estados da "
-#~ "tarefa.\n"
-#~ "    "
-
-#~ msgid "Attachments"
-#~ msgstr "Anexos"
-
-#~ msgid "Details"
-#~ msgstr "Detalles"
diff --git a/addons/project_mailgate/i18n/hr.po b/addons/project_mailgate/i18n/hr.po
deleted file mode 100644 (file)
index 15952d9..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-# Croatian translation for openobject-addons
-# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
-"POT-Creation-Date: 2012-02-08 00:37+0000\n"
-"PO-Revision-Date: 2011-12-12 08:56+0000\n"
-"Last-Translator: Tomislav Bosnjakovic <Unknown>\n"
-"Language-Team: Croatian <hr@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-08-28 06:40+0000\n"
-"X-Generator: Launchpad (build 15864)\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr "Informacije o povjesti"
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr "Zadatak"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr "Greška! Ne možete kreirati rekurzivne zadatke."
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr "Poruke"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:90
-#, python-format
-msgid "Draft"
-msgstr "Nacrt"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! Task end-date must be greater then task start-date"
-msgstr ""
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:116
-#, python-format
-msgid "Cancel"
-msgstr "Odustani"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:110
-#, python-format
-msgid "Done"
-msgstr "Izvršeno"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:96
-#, python-format
-msgid "Open"
-msgstr "Otvoreno"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:102
-#, python-format
-msgid "Pending"
-msgstr "Na čekanju"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr "Povijest"
-
-#~ msgid "Project MailGateWay"
-#~ msgstr "MailGateWay projekta"
-
-#~ msgid ""
-#~ "This module is an interface that synchronises mails with OpenERP Project "
-#~ "Task.\n"
-#~ "\n"
-#~ "It allows creating tasks as soon as a new mail arrives in our configured "
-#~ "mail server.\n"
-#~ "Moreover, it keeps track of all further communications and task states.\n"
-#~ "    "
-#~ msgstr ""
-#~ "Ovaj modul je sučelje za sinhronizaciju poruka sa OpenERP projektnim "
-#~ "zadacima.\n"
-#~ "\n"
-#~ "Omogućava kreiranje zadataka čim stigne novi e-mail na naš konfigurirani "
-#~ "mail poslužitelj.\n"
-#~ "Štoviše, čuva zapise o svim kasnijim komunikacijama i statusima zadataka.\n"
-#~ "    "
-
-#~ msgid "Attachments"
-#~ msgstr "Privici"
-
-#~ msgid "Details"
-#~ msgstr "Pojedinosti"
diff --git a/addons/project_mailgate/i18n/hu.po b/addons/project_mailgate/i18n/hu.po
deleted file mode 100644 (file)
index b1d551f..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-# Translation of OpenERP Server.
-# This file contains the translation of the following modules:
-#    * project_mailgate
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: OpenERP Server 6.0dev\n"
-"Report-Msgid-Bugs-To: support@openerp.com\n"
-"POT-Creation-Date: 2012-02-08 00:37+0000\n"
-"PO-Revision-Date: 2011-01-25 13:21+0000\n"
-"Last-Translator: NOVOTRADE RENDSZERHÁZ ( novotrade.hu ) "
-"<openerp@novotrade.hu>\n"
-"Language-Team: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=utf-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-08-28 06:40+0000\n"
-"X-Generator: Launchpad (build 15864)\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr "Előzmény"
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr "Feladat"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr "Hiba! Nem hozhat létre rekurzív feladatokat."
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr "Üzenetek"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:90
-#, python-format
-msgid "Draft"
-msgstr "Tervezet"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! Task end-date must be greater then task start-date"
-msgstr ""
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:116
-#, python-format
-msgid "Cancel"
-msgstr "Mégsem"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:110
-#, python-format
-msgid "Done"
-msgstr "Kész"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:96
-#, python-format
-msgid "Open"
-msgstr "Nyitott"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:102
-#, python-format
-msgid "Pending"
-msgstr "Függőben lévő"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr "Előzmény"
-
-#~ msgid "Attachments"
-#~ msgstr "Mellékletek"
-
-#~ msgid "Details"
-#~ msgstr "Részletek"
diff --git a/addons/project_mailgate/i18n/it.po b/addons/project_mailgate/i18n/it.po
deleted file mode 100644 (file)
index a6d5602..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-# Italian translation for openobject-addons
-# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
-"POT-Creation-Date: 2012-02-08 00:37+0000\n"
-"PO-Revision-Date: 2011-04-22 09:54+0000\n"
-"Last-Translator: simone.sandri <lexluxsox@hotmail.it>\n"
-"Language-Team: Italian <it@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-08-28 06:40+0000\n"
-"X-Generator: Launchpad (build 15864)\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr "Storico informazioni"
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr "Attività"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr "Errore ! Non è possibile creare attività ricorsive."
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr "Messaggi"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:90
-#, python-format
-msgid "Draft"
-msgstr "Bozza"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! Task end-date must be greater then task start-date"
-msgstr ""
-"Errore ! La data finale della mansione deve essere  più vecchia di quella "
-"iniziale"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:116
-#, python-format
-msgid "Cancel"
-msgstr "Annulla"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:110
-#, python-format
-msgid "Done"
-msgstr "Completato"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:96
-#, python-format
-msgid "Open"
-msgstr "Apri"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:102
-#, python-format
-msgid "Pending"
-msgstr "In sospeso"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr "Storico"
-
-#~ msgid "Attachments"
-#~ msgstr "Allegati"
-
-#~ msgid "Details"
-#~ msgstr "Dettagli"
-
-#~ msgid ""
-#~ "This module is an interface that synchronises mails with OpenERP Project "
-#~ "Task.\n"
-#~ "\n"
-#~ "It allows creating tasks as soon as a new mail arrives in our configured "
-#~ "mail server.\n"
-#~ "Moreover, it keeps track of all further communications and task states.\n"
-#~ "    "
-#~ msgstr ""
-#~ "Questo modulo è una infertaccia che sincronizza mail con le attività di "
-#~ "progetto di OpenERP.\n"
-#~ "\n"
-#~ "Esso permette di creare attività non appena una nuova mail arriva nel mail "
-#~ "server configurato.\n"
-#~ "Inoltre, tiene traccia di tutte le comunicazioni e lo stato delle attività.\n"
-#~ "    "
-
-#~ msgid "Project MailGateWay"
-#~ msgstr "Ingresso Mail di Progetto"
diff --git a/addons/project_mailgate/i18n/ja.po b/addons/project_mailgate/i18n/ja.po
deleted file mode 100644 (file)
index 2370edb..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-# Japanese translation for openobject-addons
-# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
-"POT-Creation-Date: 2012-02-08 00:37+0000\n"
-"PO-Revision-Date: 2012-06-10 03:13+0000\n"
-"Last-Translator: Akira Hiyama <Unknown>\n"
-"Language-Team: Japanese <ja@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-08-28 06:40+0000\n"
-"X-Generator: Launchpad (build 15864)\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr "履歴情報"
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr "タスク"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr "エラー。再帰的なタスクを作成することはできません。"
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr "メッセージ"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:90
-#, python-format
-msgid "Draft"
-msgstr "ドラフト"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! Task end-date must be greater then task start-date"
-msgstr "エラー。タスクの終了日は開始日以降の日付に設定しなければなりません。"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:116
-#, python-format
-msgid "Cancel"
-msgstr "キャンセル"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:110
-#, python-format
-msgid "Done"
-msgstr "完了"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:96
-#, python-format
-msgid "Open"
-msgstr "開く"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:102
-#, python-format
-msgid "Pending"
-msgstr "保留中"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr "履歴"
diff --git a/addons/project_mailgate/i18n/lv.po b/addons/project_mailgate/i18n/lv.po
deleted file mode 100644 (file)
index 4bb1683..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-# Latvian translation for openobject-addons
-# Copyright (c) 2010 Rosetta Contributors and Canonical Ltd 2010
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2010.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
-"POT-Creation-Date: 2012-02-08 00:37+0000\n"
-"PO-Revision-Date: 2010-12-31 10:38+0000\n"
-"Last-Translator: OpenERP Administrators <Unknown>\n"
-"Language-Team: Latvian <lv@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-08-28 06:40+0000\n"
-"X-Generator: Launchpad (build 15864)\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr "Vēstures informācija"
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr "Uzdevums"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr "Kļūda! Nedrīkst veidot rekursīvus uzdevumus."
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr "Ziņojumi"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:90
-#, python-format
-msgid "Draft"
-msgstr "Melnraksts"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! Task end-date must be greater then task start-date"
-msgstr ""
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:116
-#, python-format
-msgid "Cancel"
-msgstr "Atcelt"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:110
-#, python-format
-msgid "Done"
-msgstr "Pabeigts"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:96
-#, python-format
-msgid "Open"
-msgstr "Atvērt"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:102
-#, python-format
-msgid "Pending"
-msgstr "Neizlemts"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr "Vēsture"
-
-#~ msgid "Project MailGateWay"
-#~ msgstr "Projekta MailGateWay"
-
-#~ msgid "Attachments"
-#~ msgstr "Piesaistnes"
-
-#~ msgid "Details"
-#~ msgstr "Sīkāka informācija"
diff --git a/addons/project_mailgate/i18n/mn.po b/addons/project_mailgate/i18n/mn.po
deleted file mode 100644 (file)
index 157b726..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-# Mongolian translation for openobject-addons
-# Copyright (c) 2010 Rosetta Contributors and Canonical Ltd 2010
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2010.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
-"POT-Creation-Date: 2012-02-08 00:37+0000\n"
-"PO-Revision-Date: 2010-12-21 13:53+0000\n"
-"Last-Translator: OpenERP Administrators <Unknown>\n"
-"Language-Team: Mongolian <mn@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-08-28 06:40+0000\n"
-"X-Generator: Launchpad (build 15864)\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr "Түүх"
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr "Ажил"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr "Алдаа ! Та рекурсив цэс үүсгэж болохгүй!"
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr "Мессежүүд"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:90
-#, python-format
-msgid "Draft"
-msgstr "Ноорог"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! Task end-date must be greater then task start-date"
-msgstr ""
-"Алдаа ! Даалгаврын дуусах хугацаа нь эхлэх хугацаанаасаа хойно байх ёстой"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:116
-#, python-format
-msgid "Cancel"
-msgstr "Болих"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:110
-#, python-format
-msgid "Done"
-msgstr "Хийсэн"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:96
-#, python-format
-msgid "Open"
-msgstr "Нээх"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:102
-#, python-format
-msgid "Pending"
-msgstr "Хүлээгдэж буй"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr "Түүх"
-
-#~ msgid "Project MailGateWay"
-#~ msgstr "MailGateWay Төсөл"
-
-#~ msgid "Details"
-#~ msgstr "Нарийвчлал"
diff --git a/addons/project_mailgate/i18n/nl.po b/addons/project_mailgate/i18n/nl.po
deleted file mode 100644 (file)
index 1fffc91..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-# Dutch translation for openobject-addons
-# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
-"POT-Creation-Date: 2012-02-08 00:37+0000\n"
-"PO-Revision-Date: 2011-01-18 20:55+0000\n"
-"Last-Translator: Douwe Wullink (Dypalio) <Unknown>\n"
-"Language-Team: Dutch <nl@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-08-28 06:40+0000\n"
-"X-Generator: Launchpad (build 15864)\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr "Historie informatie"
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr "Taak"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr "Fout! U kunt geen recursieve taken aanmaken."
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr "Berichten"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:90
-#, python-format
-msgid "Draft"
-msgstr "Concept"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! Task end-date must be greater then task start-date"
-msgstr "Fout! Einddatum taak moet groter zijn dat begindatum taak"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:116
-#, python-format
-msgid "Cancel"
-msgstr "Annuleren"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:110
-#, python-format
-msgid "Done"
-msgstr "Gereed"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:96
-#, python-format
-msgid "Open"
-msgstr "Open"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:102
-#, python-format
-msgid "Pending"
-msgstr "Wachtend"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr "Geschiedenis"
-
-#~ msgid ""
-#~ "This module is an interface that synchronises mails with OpenERP Project "
-#~ "Task.\n"
-#~ "\n"
-#~ "It allows creating tasks as soon as a new mail arrives in our configured "
-#~ "mail server.\n"
-#~ "Moreover, it keeps track of all further communications and task states.\n"
-#~ "    "
-#~ msgstr ""
-#~ "Deze module is een interface dat mail synchroniseert met OpenERP Project "
-#~ "taak.\n"
-#~ "\n"
-#~ "Het maakt nieuwe taken aan zodra mail binnenkomt op de ingestelde "
-#~ "mailserver.\n"
-#~ "Daarbij houdt het alle verdere communicaties en taak statussen bij.\n"
-#~ "    "
-
-#~ msgid "Attachments"
-#~ msgstr "Bijlagen"
-
-#~ msgid "Details"
-#~ msgstr "Details"
-
-#~ msgid "Project MailGateWay"
-#~ msgstr "Project MailGateWay"
diff --git a/addons/project_mailgate/i18n/nl_BE.po b/addons/project_mailgate/i18n/nl_BE.po
deleted file mode 100644 (file)
index 40124ec..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-# Dutch (Belgium) translation for openobject-addons
-# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
-"POT-Creation-Date: 2012-02-08 00:37+0000\n"
-"PO-Revision-Date: 2012-03-01 15:19+0000\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: Dutch (Belgium) <nl_BE@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-08-28 06:40+0000\n"
-"X-Generator: Launchpad (build 15864)\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr "Historiekinformatie"
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr "Taak"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr "U kunt niet dezelfde taken maken."
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr "Berichten"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:90
-#, python-format
-msgid "Draft"
-msgstr "Voorlopig"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! Task end-date must be greater then task start-date"
-msgstr ""
-"Let op: de einddatum van de taak moet na de begindatum van de taak liggen"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:116
-#, python-format
-msgid "Cancel"
-msgstr "Annuleren"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:110
-#, python-format
-msgid "Done"
-msgstr "Gereed"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:96
-#, python-format
-msgid "Open"
-msgstr "Open"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:102
-#, python-format
-msgid "Pending"
-msgstr "Wachtend"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr "Historiek"
-
-#~ msgid ""
-#~ "This module is an interface that synchronises mails with OpenERP Project "
-#~ "Task.\n"
-#~ "\n"
-#~ "It allows creating tasks as soon as a new mail arrives in our configured "
-#~ "mail server.\n"
-#~ "Moreover, it keeps track of all further communications and task states.\n"
-#~ "    "
-#~ msgstr ""
-#~ "Deze module is een interface waarmee mails met OpenERP-projecttaken worden "
-#~ "gesynchroniseerd.\n"
-#~ "\n"
-#~ "Hiermee worden taken gemaakt zodra een nieuwe mail binnenkomt via de "
-#~ "ingestelde mailserver.\n"
-#~ "Bovendien worden alle communicatie en taakstatussen bijgehouden.\n"
-#~ "    "
-
-#~ msgid "Attachments"
-#~ msgstr "Bijlagen"
-
-#~ msgid "Project MailGateWay"
-#~ msgstr "Projectmailgateway"
-
-#~ msgid "Details"
-#~ msgstr "Details"
diff --git a/addons/project_mailgate/i18n/pl.po b/addons/project_mailgate/i18n/pl.po
deleted file mode 100644 (file)
index 448e4b4..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-# Polish translation for openobject-addons
-# Copyright (c) 2010 Rosetta Contributors and Canonical Ltd 2010
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2010.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
-"POT-Creation-Date: 2012-02-08 00:37+0000\n"
-"PO-Revision-Date: 2010-11-28 12:29+0000\n"
-"Last-Translator: OpenERP Administrators <Unknown>\n"
-"Language-Team: Polish <pl@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-08-28 06:40+0000\n"
-"X-Generator: Launchpad (build 15864)\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr "Informacja o historii"
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr "Zadanie"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr ""
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr "Wiadomosći"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:90
-#, python-format
-msgid "Draft"
-msgstr "Projekt"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! Task end-date must be greater then task start-date"
-msgstr ""
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:116
-#, python-format
-msgid "Cancel"
-msgstr "Anuluj"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:110
-#, python-format
-msgid "Done"
-msgstr "Wykonano"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:96
-#, python-format
-msgid "Open"
-msgstr "Otwórz"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:102
-#, python-format
-msgid "Pending"
-msgstr "Oczekujące"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr "Historia"
-
-#~ msgid "Project MailGateWay"
-#~ msgstr "MailGateWay dla projektu"
-
-#~ msgid ""
-#~ "This module is an interface that synchronises mails with OpenERP Project "
-#~ "Task.\n"
-#~ "\n"
-#~ "It allows creating tasks as soon as a new mail arrives in our configured "
-#~ "mail server.\n"
-#~ "Moreover, it keeps track of all further communications and task states.\n"
-#~ "    "
-#~ msgstr ""
-#~ "Ten moduł jest interfejsem do synchronizacji poczty z zadaniami w projektach "
-#~ "OpenERP.\n"
-#~ "\n"
-#~ "Pozwala tworzyć projekty po otrzymaniu wiadomości przez odpowiednio "
-#~ "skonfigurowany\n"
-#~ "server pocztowy. Co więcej, utrzymuje on w jednym miejscu dalszą komunikację "
-#~ "i stany zadań.\n"
-#~ "    "
-
-#~ msgid "Details"
-#~ msgstr "Szczegóły"
diff --git a/addons/project_mailgate/i18n/project_mailgate.pot b/addons/project_mailgate/i18n/project_mailgate.pot
deleted file mode 100644 (file)
index acb1e4e..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-# Translation of OpenERP Server.
-# This file contains the translation of the following modules:
-#      * project_mailgate
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: OpenERP Server 6.1rc1\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-02-08 00:37+0000\n"
-"PO-Revision-Date: 2012-02-08 00:37+0000\n"
-"Last-Translator: <>\n"
-"Language-Team: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: \n"
-"Plural-Forms: \n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr ""
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr ""
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr ""
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr ""
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:90
-#, python-format
-msgid "Draft"
-msgstr ""
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! Task end-date must be greater then task start-date"
-msgstr ""
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:116
-#, python-format
-msgid "Cancel"
-msgstr ""
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:110
-#, python-format
-msgid "Done"
-msgstr ""
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:96
-#, python-format
-msgid "Open"
-msgstr ""
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:102
-#, python-format
-msgid "Pending"
-msgstr ""
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr ""
-
diff --git a/addons/project_mailgate/i18n/pt.po b/addons/project_mailgate/i18n/pt.po
deleted file mode 100644 (file)
index 8a907a4..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-# Portuguese translation for openobject-addons
-# Copyright (c) 2010 Rosetta Contributors and Canonical Ltd 2010
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2010.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
-"POT-Creation-Date: 2012-02-08 00:37+0000\n"
-"PO-Revision-Date: 2010-12-12 10:12+0000\n"
-"Last-Translator: OpenERP Administrators <Unknown>\n"
-"Language-Team: Portuguese <pt@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-08-28 06:40+0000\n"
-"X-Generator: Launchpad (build 15864)\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr "Informação do Histórico"
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr "Tarefa"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr "Erro ! Não se pode criar tarefas recursivas"
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr "Mensagens"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:90
-#, python-format
-msgid "Draft"
-msgstr "Rascunho"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! Task end-date must be greater then task start-date"
-msgstr "Erro !Data final da tarefa dever ser posterior à data inicial"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:116
-#, python-format
-msgid "Cancel"
-msgstr "Cancelar"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:110
-#, python-format
-msgid "Done"
-msgstr "Concluído"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:96
-#, python-format
-msgid "Open"
-msgstr "Abrir"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:102
-#, python-format
-msgid "Pending"
-msgstr "Pendente"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr "Histórico"
-
-#~ msgid "Details"
-#~ msgstr "Detalhes"
-
-#~ msgid "Attachments"
-#~ msgstr "Anexos"
diff --git a/addons/project_mailgate/i18n/pt_BR.po b/addons/project_mailgate/i18n/pt_BR.po
deleted file mode 100644 (file)
index cd084a2..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-# Brazilian Portuguese translation for openobject-addons
-# Copyright (c) 2010 Rosetta Contributors and Canonical Ltd 2010
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2010.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
-"POT-Creation-Date: 2012-02-08 00:37+0000\n"
-"PO-Revision-Date: 2011-01-17 11:53+0000\n"
-"Last-Translator: Emerson <Unknown>\n"
-"Language-Team: Brazilian Portuguese <pt_BR@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-08-28 06:40+0000\n"
-"X-Generator: Launchpad (build 15864)\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr "Informação Histórica"
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr "Tarefa"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr "Erro! Você não pode criar tarefas recursivas."
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr "Mensagens"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:90
-#, python-format
-msgid "Draft"
-msgstr "Provisório"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! Task end-date must be greater then task start-date"
-msgstr "Erro ! A data final deve ser maior do que a data inicial"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:116
-#, python-format
-msgid "Cancel"
-msgstr "Cancelar"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:110
-#, python-format
-msgid "Done"
-msgstr "Concluído"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:96
-#, python-format
-msgid "Open"
-msgstr "Abrir"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:102
-#, python-format
-msgid "Pending"
-msgstr "Pendente"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr "Histórico"
-
-#~ msgid "Details"
-#~ msgstr "Detalhes"
-
-#~ msgid ""
-#~ "This module is an interface that synchronises mails with OpenERP Project "
-#~ "Task.\n"
-#~ "\n"
-#~ "It allows creating tasks as soon as a new mail arrives in our configured "
-#~ "mail server.\n"
-#~ "Moreover, it keeps track of all further communications and task states.\n"
-#~ "    "
-#~ msgstr ""
-#~ "Este módulo é uma interface que sincroniza emails com as Tarefas de Projetos "
-#~ "OpenERP.\n"
-#~ "\n"
-#~ "Permite a criação de tarefas tão logo um novo email chegue no nosso servidor "
-#~ "de email configurado.\n"
-#~ "Além disso, mantém o controle de todas as demais comunicações e estados das "
-#~ "tarefas.\n"
-#~ "    "
-
-#~ msgid "Project MailGateWay"
-#~ msgstr "Projeto Gateway de Email"
-
-#~ msgid "Attachments"
-#~ msgstr "Anexos"
diff --git a/addons/project_mailgate/i18n/ro.po b/addons/project_mailgate/i18n/ro.po
deleted file mode 100644 (file)
index dacd3b4..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-# Romanian translation for openobject-addons
-# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
-"POT-Creation-Date: 2012-02-08 00:37+0000\n"
-"PO-Revision-Date: 2012-01-13 11:48+0000\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: Romanian <ro@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-08-28 06:40+0000\n"
-"X-Generator: Launchpad (build 15864)\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr "Istoric Informatii"
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr "Sarcina"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr "Eroare ! Nu puteti crea sarcini recursive."
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr "Mesaje"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:90
-#, python-format
-msgid "Draft"
-msgstr "Ciorna"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! Task end-date must be greater then task start-date"
-msgstr ""
-"Eroare ! Data de sfarsit a sarcinii trebuie sa fie mai mare decat data de "
-"inceput"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:116
-#, python-format
-msgid "Cancel"
-msgstr "Anulati"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:110
-#, python-format
-msgid "Done"
-msgstr "Efectuat"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:96
-#, python-format
-msgid "Open"
-msgstr "Deschideti"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:102
-#, python-format
-msgid "Pending"
-msgstr "In asteptare"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr "Istoric"
-
-#~ msgid "Project MailGateWay"
-#~ msgstr "MailGAteWay Proiect"
-
-#~ msgid ""
-#~ "This module is an interface that synchronises mails with OpenERP Project "
-#~ "Task.\n"
-#~ "\n"
-#~ "It allows creating tasks as soon as a new mail arrives in our configured "
-#~ "mail server.\n"
-#~ "Moreover, it keeps track of all further communications and task states.\n"
-#~ "    "
-#~ msgstr ""
-#~ "Acest modul este o interfată care sincronizează email-urile cu Activitatea "
-#~ "de Proiect OpenERP.\n"
-#~ "\n"
-#~ "Permite crearea de sarcini de indată ce un nou email soseste in serverul de "
-#~ "mail configurat.\n"
-#~ "In plus, tine evidenta tuturor comunicatiilor viitoare si a stărilor "
-#~ "activitătilor.\n"
-#~ "    "
-
-#~ msgid "Attachments"
-#~ msgstr "Atașamente"
-
-#~ msgid "Details"
-#~ msgstr "Detalii"
diff --git a/addons/project_mailgate/i18n/sv.po b/addons/project_mailgate/i18n/sv.po
deleted file mode 100644 (file)
index 02d5c55..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-# Swedish translation for openobject-addons
-# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
-"POT-Creation-Date: 2012-02-08 00:37+0000\n"
-"PO-Revision-Date: 2012-06-18 23:27+0000\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: Swedish <sv@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-08-28 06:40+0000\n"
-"X-Generator: Launchpad (build 15864)\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr "Historikinformation"
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr "Aktivitet"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr "Fel ! Du kan inte skapa rekursiva aktiviteter"
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr "Meddelanden"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:90
-#, python-format
-msgid "Draft"
-msgstr "Preliminär"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! Task end-date must be greater then task start-date"
-msgstr "Fel ! Aktivitetens slutdatum måste komma efter startdatumet"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:116
-#, python-format
-msgid "Cancel"
-msgstr "Avbryt"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:110
-#, python-format
-msgid "Done"
-msgstr "Färdig"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:96
-#, python-format
-msgid "Open"
-msgstr "Öppen"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:102
-#, python-format
-msgid "Pending"
-msgstr "Väntande"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr "Historik"
diff --git a/addons/project_mailgate/i18n/tr.po b/addons/project_mailgate/i18n/tr.po
deleted file mode 100644 (file)
index 1f8a351..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-# Turkish translation for openobject-addons
-# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
-"POT-Creation-Date: 2012-02-08 00:37+0000\n"
-"PO-Revision-Date: 2012-01-25 17:33+0000\n"
-"Last-Translator: Ahmet Altınışık <Unknown>\n"
-"Language-Team: Turkish <tr@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-08-28 06:40+0000\n"
-"X-Generator: Launchpad (build 15864)\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr ""
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr "Görev"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr ""
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr "Mesajlar"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:90
-#, python-format
-msgid "Draft"
-msgstr "Taslak"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! Task end-date must be greater then task start-date"
-msgstr ""
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:116
-#, python-format
-msgid "Cancel"
-msgstr "İptal"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:110
-#, python-format
-msgid "Done"
-msgstr "Tamamlandı"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:96
-#, python-format
-msgid "Open"
-msgstr "Açık"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:102
-#, python-format
-msgid "Pending"
-msgstr "Bekleyen"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr ""
diff --git a/addons/project_mailgate/i18n/zh_CN.po b/addons/project_mailgate/i18n/zh_CN.po
deleted file mode 100644 (file)
index c1b8ea0..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-# Chinese (Simplified) translation for openobject-addons
-# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011
-# This file is distributed under the same license as the openobject-addons package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: openobject-addons\n"
-"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
-"POT-Creation-Date: 2012-02-08 00:37+0000\n"
-"PO-Revision-Date: 2011-06-28 16:25+0000\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: Chinese (Simplified) <zh_CN@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2012-08-28 06:40+0000\n"
-"X-Generator: Launchpad (build 15864)\n"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History Information"
-msgstr "日志信息"
-
-#. module: project_mailgate
-#: model:ir.model,name:project_mailgate.model_project_task
-msgid "Task"
-msgstr "任务"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! You cannot create recursive tasks."
-msgstr "错误!不能创建循环引用的任务"
-
-#. module: project_mailgate
-#: field:project.task,message_ids:0
-msgid "Messages"
-msgstr "消息"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:90
-#, python-format
-msgid "Draft"
-msgstr "草稿"
-
-#. module: project_mailgate
-#: constraint:project.task:0
-msgid "Error ! Task end-date must be greater then task start-date"
-msgstr "错误!任务结束日期必须大于任务开始日期"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:116
-#, python-format
-msgid "Cancel"
-msgstr "取消"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:110
-#, python-format
-msgid "Done"
-msgstr "完成"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:96
-#, python-format
-msgid "Open"
-msgstr "未结"
-
-#. module: project_mailgate
-#: code:addons/project_mailgate/project_mailgate.py:102
-#, python-format
-msgid "Pending"
-msgstr "等待中"
-
-#. module: project_mailgate
-#: view:project.task:0
-msgid "History"
-msgstr "日志"
-
-#~ msgid "Project MailGateWay"
-#~ msgstr "Project MailGateWay"
-
-#~ msgid ""
-#~ "This module is an interface that synchronises mails with OpenERP Project "
-#~ "Task.\n"
-#~ "\n"
-#~ "It allows creating tasks as soon as a new mail arrives in our configured "
-#~ "mail server.\n"
-#~ "Moreover, it keeps track of all further communications and task states.\n"
-#~ "    "
-#~ msgstr ""
-#~ "此模块是邮件和任务的接口。\n"
-#~ "允许在特定邮件服务器收到邮件后马上创建任务。\n"
-#~ "还能跟踪进一步的沟通和任务阶段\n"
-#~ "    "
-
-#~ msgid "Attachments"
-#~ msgstr "附件"
-
-#~ msgid "Details"
-#~ msgstr "详细信息"
diff --git a/addons/project_mailgate/project_mailgate.py b/addons/project_mailgate/project_mailgate.py
deleted file mode 100644 (file)
index ba3cd70..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-#    OpenERP, Open Source Management Solution
-#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
-#
-#    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 binascii
-from osv import fields, osv
-from tools.translate import _
-import tools
-
-class project_tasks(osv.osv):
-    _inherit = 'project.task'
-
-    def message_new(self, cr, uid, msg, custom_values=None, context=None):
-        """ Overrides mail_thread message_new that is called by the mailgateway
-            through message_process.
-            This override updates the document according to the email.
-        """
-        if custom_values is None: custom_values = {}
-        custom_values.update({
-            'name': subject,
-            'planned_hours': 0.0,
-            'subject': msg.get('subject'),
-        })
-        custom_values.update(self.message_partner_by_email(cr, uid, msg.get('from', False), context=context))
-        return super(project_tasks,self).message_new(cr, uid, msg, custom_values=custom_values, context=context)
-
-    def message_update(self, cr, uid, ids, msg, update_vals=None, context=None):
-        """ Overrides mail_thread message_update that is called by the mailgateway
-            through message_process.
-            This method updates the task according to the email.
-        """
-        if update_vals is None: update_vals = {}
-        act = False
-        maps = {
-            'cost':'planned_hours',
-        }
-        for line in msg['body_text'].split('\n'):
-            line = line.strip()
-            res = tools.misc.command_re.match(line)
-            if res:
-                match = res.group(1).lower()
-                field = maps.get(match)
-                if field:
-                    try:
-                        update_vals[field] = float(res.group(2).lower())
-                    except (ValueError, TypeError):
-                        pass
-                elif match.lower() == 'state' \
-                        and res.group(2).lower() in ['cancel','close','draft','open','pending']:
-                    act = 'do_%s' % res.group(2).lower()
-        if act:
-            getattr(self,act)(cr, uid, ids, context=context)
-        return super(project_tasks,self).message_update(cr, uid, msg, update_vals=update_vals, context=context)
-
-    def message_thread_followers(self, cr, uid, ids, context=None):
-        followers = super(project_tasks,self).message_thread_followers(cr, uid, ids, context=context)
-        for task in self.browse(cr, uid, followers.keys(), context=context):
-            task_followers = set(followers[task.id])
-            task_followers.add(task.user_id.email)
-            followers[task.id] = filter(None, task_followers)
-        return followers
-
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
index b97da91..daad446 100644 (file)
     </div>
 </div>
             ]]></field>
-            <field name="body_text"><![CDATA[
-Hello${object.partner_id.name and ' ' or ''}${object.partner_id.name or ''},
-
-Here is a ${object.state in ('draft', 'sent') and 'request for quotation' or 'purchase order confirmation'} from ${object.company_id.name}:
-       | Order number: *${object.name}*
-       | Order total: *${object.amount_total} ${object.pricelist_id.currency_id.name}*
-       | Order date: ${object.date_order}
-       % if object.origin:
-       | Order reference: ${object.origin}
-       % endif
-       % if object.partner_ref:
-       | Your reference: ${object.partner_ref}<br />
-       % endif
-       | Your contact: ${object.validator.name} ${object.validator.email and '<%s>'%(object.validator.email) or ''}
-
-You can view the ${object.state in ('draft', 'sent') and 'request for quotation' or 'order confirmation'} and download it using the following link:
-    ${ctx.get('edi_web_url_view') or 'n/a'}
-
-If you have any question, do not hesitate to contact us.
-
-Thank you!
-
-
---
-${object.validator.name} ${object.validator.email and '<%s>'%(object.validator.email) or ''}
-${object.company_id.name}
-% if object.company_id.street:
-${object.company_id.street or ''}
-% endif
-% if object.company_id.street2:
-${object.company_id.street2}
-% endif
-% if object.company_id.city or object.company_id.zip:
-${object.company_id.zip or ''} ${object.company_id.city or ''}
-% endif
-% if object.company_id.country_id:
-${object.company_id.state_id and ('%s, ' % object.company_id.state_id.name) or ''} ${object.company_id.country_id.name or ''}
-% endif
-% if object.company_id.phone:
-Phone: ${object.company_id.phone}
-% endif
-% if object.company_id.website:
-${object.company_id.website or ''}
-% endif
-            ]]></field>
         </record>
     </data>
 </openerp>
index 44355e2..a7fd4cf 100644 (file)
@@ -1767,7 +1767,7 @@ msgid ""
 msgstr ""
 
 #. module: purchase
-#: model:email.template,body_text:purchase.email_template_edi_purchase
+#: model:email.template,body:purchase.email_template_edi_purchase
 msgid ""
 "\n"
 "Hello${object.partner_address_id.name and ' ' or ''}${object."
index 756016d..592bf78 100644 (file)
@@ -220,7 +220,7 @@ class purchase_order(osv.osv):
         ('name_uniq', 'unique(name, company_id)', 'Order Reference must be unique per Company!'),
     ]
     _name = "purchase.order"
-    _inherit = ['ir.needaction_mixin', 'mail.thread']
+    _inherit = ['mail.thread', 'ir.needaction_mixin']
     _description = "Purchase Order"
     _order = "name desc"
 
@@ -731,24 +731,15 @@ class purchase_order(osv.osv):
     # OpenChatter methods and notifications
     # --------------------------------------
 
-    def get_needaction_user_ids(self, cr, uid, ids, context=None):
-        result = super(purchase_order, self).get_needaction_user_ids(cr, uid, ids, context=context)
-        for obj in self.browse(cr, uid, ids, context=context):
-            if obj.state == 'approved':
-                result[obj.id].append(obj.validator.id)
-        return result
-
-    def message_get_monitored_follower_fields(self, cr, uid, ids, context=None):
-        """ Add 'validator' to the monitored fields """
-        res = super(purchase_order, self).message_get_monitored_follower_fields(cr, uid, ids, context=context)
-        return res + ['validator']
+    def needaction_domain_get(self, cr, uid, ids, context=None):
+        return [('state', '=', 'draft')]
 
     def create_send_note(self, cr, uid, ids, context=None):
-        return self.message_append_note(cr, uid, ids, body=_("Request for quotation <b>created</b>."), context=context)
+        return self.message_post(cr, uid, ids, body=_("Request for quotation <b>created</b>."), context=context)
 
     def confirm_send_note(self, cr, uid, ids, context=None):
         for obj in self.browse(cr, uid, ids, context=context):
-            self.message_append_note(cr, uid, [obj.id], body=_("Quotation for <em>%s</em> <b>converted</b> to a Purchase Order of %s %s.") % (obj.partner_id.name, obj.amount_total, obj.pricelist_id.currency_id.symbol), context=context)
+            self.message_post(cr, uid, [obj.id], body=_("Quotation for <em>%s</em> <b>converted</b> to a Purchase Order of %s %s.") % (obj.partner_id.name, obj.amount_total, obj.pricelist_id.currency_id.symbol), context=context)
 
     def shipment_send_note(self, cr, uid, ids, picking_id, context=None):
         for order in self.browse(cr, uid, ids, context=context):
@@ -757,25 +748,25 @@ class purchase_order(osv.osv):
                 # convert it to the user TZ and re-render it with %Z to add the timezone
                 picking_datetime = fields.DT.datetime.strptime(picking.min_date, DEFAULT_SERVER_DATETIME_FORMAT)
                 picking_date_str = fields.datetime.context_timestamp(cr, uid, picking_datetime, context=context).strftime(DATETIME_FORMATS_MAP['%+'] + " (%Z)")
-                self.message_append_note(cr, uid, [order.id], body=_("Shipment <em>%s</em> <b>scheduled</b> for %s.") % (picking.name, picking_date_str), context=context)
+                self.message_post(cr, uid, [order.id], body=_("Shipment <em>%s</em> <b>scheduled</b> for %s.") % (picking.name, picking_date_str), context=context)
 
     def invoice_send_note(self, cr, uid, ids, invoice_id, context=None):
         for order in self.browse(cr, uid, ids, context=context):
             for invoice in (inv for inv in order.invoice_ids if inv.id == invoice_id):
-                self.message_append_note(cr, uid, [order.id], body=_("Draft Invoice of %s %s is <b>waiting for validation</b>.") % (invoice.amount_total, invoice.currency_id.symbol), context=context)
+                self.message_post(cr, uid, [order.id], body=_("Draft Invoice of %s %s is <b>waiting for validation</b>.") % (invoice.amount_total, invoice.currency_id.symbol), context=context)
 
     def shipment_done_send_note(self, cr, uid, ids, context=None):
-        self.message_append_note(cr, uid, ids, body=_("""Shipment <b>received</b>."""), context=context)
+        self.message_post(cr, uid, ids, body=_("""Shipment <b>received</b>."""), context=context)
 
     def invoice_done_send_note(self, cr, uid, ids, context=None):
-        self.message_append_note(cr, uid, ids, body=_("Invoice <b>paid</b>."), context=context)
+        self.message_post(cr, uid, ids, body=_("Invoice <b>paid</b>."), context=context)
 
     def draft_send_note(self, cr, uid, ids, context=None):
-        return self.message_append_note(cr, uid, ids, body=_("Purchase Order has been set to <b>draft</b>."), context=context)
+        return self.message_post(cr, uid, ids, body=_("Purchase Order has been set to <b>draft</b>."), context=context)
 
     def cancel_send_note(self, cr, uid, ids, context=None):
         for obj in self.browse(cr, uid, ids, context=context):
-            self.message_append_note(cr, uid, [obj.id], body=_("Purchase Order for <em>%s</em> <b>cancelled</b>.") % (obj.partner_id.name), context=context)
+            self.message_post(cr, uid, [obj.id], body=_("Purchase Order for <em>%s</em> <b>cancelled</b>.") % (obj.partner_id.name), context=context)
 
 purchase_order()
 
index 75074d4..693d18e 100644 (file)
@@ -3,14 +3,15 @@
     <data noupdate="1">
 
         <!-- notify all employees of module installation -->
-        <function model="mail.group" name="message_append_note">
-            <!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
-            <value eval="[ref('mail.group_all_employees')]"/>
-            <value>Module Purchase Management has been installed.</value>
-            <value>From the top menu Purchases, create purchase orders to buy products from your suppliers, encode supplier invoices and manage your payments.
+        <record model="mail.message" id="module_install_notification">
+            <field name="model">mail.group</field>
+            <field name="res_id" ref="mail.group_all_employees"/>
+            <field name="type">notification</field>
+            <field name="subject">Purchase Management application installed!</field>
+            <field name="body">From the top menu Purchases, create purchase orders to buy products from your suppliers, enter supplier invoices and manage payments.
 
-You can also manage purchase requisitions, see the Purchase Settings.</value>
-        </function>
+You can also manage purchase requisitions, see also the Purchase Settings.</field>
+        </record>
 
         <record id="req_link_purchase_order" model="res.request.link">
             <field name="name">Purchase Order</field>
index d133e4a..ab5df67 100644 (file)
             <field name="arch" type="xml">
                 <search string="Search Purchase Order">
                     <field name="name" string="Reference"/>
-                    <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="needaction_pending" domain="[('needaction_pending','=',True)]"/>
+                    <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/>
                     <separator/>
                     <filter icon="terp-document-new" name="draft" string="Quotations" domain="[('state','=','draft')]" help="Purchase orders which are in draft state"/>
                     <filter icon="terp-check" name="approved" string="Purchase Orders" domain="[('state','not in',('draft','cancel'))]" help="Approved purchase orders"/>
             <field name="name">purchase.order.tree</field>
             <field name="model">purchase.order</field>
             <field name="arch" type="xml">
-                <tree fonts="bold:needaction_pending==True" colors="grey:state=='cancel';blue:state in ('confirmed');red:state in ('except_invoice','except_picking')" string="Purchase Order">
-                    <field name="needaction_pending" invisible="1"/>
+                <tree fonts="bold:message_unread==True" colors="grey:state=='cancel';blue:state in ('wait','confirmed');red:state in ('except_invoice','except_picking')" string="Purchase Order">
+                    <field name="message_unread" invisible="1"/>
                     <field name="name" string="Reference"/>
                     <field name="date_order" />
                     <field name="partner_id"/>
index cf02947..90a5244 100644 (file)
@@ -31,7 +31,7 @@ import decimal_precision as dp
 class purchase_requisition(osv.osv):
     _name = "purchase.requisition"
     _description="Purchase Requisition"
-    _inherit = ['ir.needaction_mixin', 'mail.thread']
+    _inherit = ['mail.thread', 'ir.needaction_mixin']
     _columns = {
         'name': fields.char('Requisition Reference', size=32,required=True),
         'origin': fields.char('Source', size=32),
@@ -91,16 +91,16 @@ class purchase_requisition(osv.osv):
         return True
 
     def in_progress_send_note(self, cr, uid, ids, context=None):
-        self.message_append_note(cr, uid, ids, body=_("Draft Requisition has been <b>sent to suppliers</b>."), context=context)
+        self.message_post(cr, uid, ids, body=_("Draft Requisition has been <b>sent to suppliers</b>."), context=context)
     
     def reset_send_note(self, cr, uid, ids, context=None):
-        self.message_append_note(cr, uid, ids, body=_("Purchase Requisition has been set to <b>draft</b>."), context=context)
+        self.message_post(cr, uid, ids, body=_("Purchase Requisition has been set to <b>draft</b>."), context=context)
      
     def done_to_send_note(self, cr, uid, ids, context=None):
-        self.message_append_note(cr, uid, ids, body=_("Purchase Requisition has been <b>done</b>."), context=context)
+        self.message_post(cr, uid, ids, body=_("Purchase Requisition has been <b>done</b>."), context=context)
         
     def cancel_send_note(self, cr, uid, ids, context=None):
-        self.message_append_note(cr, uid, ids, body=_("Purchase Requisition has been <b>cancelled</b>."), context=context)
+        self.message_post(cr, uid, ids, body=_("Purchase Requisition has been <b>cancelled</b>."), context=context)
 
     def _planned_date(self, requisition, delay=0.0):
         company = requisition.company_id
@@ -184,7 +184,7 @@ class purchase_requisition(osv.osv):
         return res
     
     def create_send_note(self, cr, uid, ids, context=None):
-        return self.message_append_note(cr, uid, ids, body=_("Purchase Requisition has been <b>created</b>."), context=context)  
+        return self.message_post(cr, uid, ids, body=_("Purchase Requisition has been <b>created</b>."), context=context)  
 
     def create(self, cr, uid, vals, context=None):
         requisition =  super(purchase_requisition, self).create(cr, uid, vals, context=context)
@@ -194,27 +194,6 @@ class purchase_requisition(osv.osv):
 
 purchase_requisition()
 
-class mail_message(osv.osv):
-    _inherit = 'mail.message'
-    
-    def schedule_with_attach(self, cr, uid, email_from, email_to, subject, body, model=False, email_cc=None,
-                             email_bcc=None, reply_to=False, attachments=None, message_id=False, references=False,
-                             res_id=False, content_subtype='plain', headers=None, mail_server_id=False, auto_delete=False,
-                             context=None):
-        result = super(mail_message, self).schedule_with_attach(cr, uid, email_from, email_to, subject, body, model=model, email_cc=email_cc,
-            email_bcc=email_bcc, reply_to=reply_to, attachments=attachments, message_id=message_id, references=references,
-            res_id=res_id, content_subtype=content_subtype, headers=headers, mail_server_id=mail_server_id, auto_delete=auto_delete,
-            context=context)
-        # check model is purchase.order
-        if model and model == 'purchase.order' and res_id:
-            requisition_id = self.pool.get('purchase.order').browse(cr, uid, res_id, context=context).requisition_id
-            if requisition_id:
-                result = self.schedule_with_attach(cr, uid, email_from, email_to, subject, body, model='purchase.requisition', email_cc=email_cc,
-                    email_bcc=email_bcc, reply_to=reply_to, attachments=attachments, message_id=message_id, references=references,
-                    res_id=requisition_id.id, content_subtype=content_subtype, headers=headers, mail_server_id=mail_server_id, auto_delete=auto_delete,
-                    context=context)
-        return result
-
 class purchase_requisition_line(osv.osv):
 
     _name = "purchase.requisition.line"
index d4e8e3e..1fafec9 100644 (file)
         <field name="name">purchase.requisition.tree</field>
         <field name="model">purchase.requisition</field>
         <field name="arch" type="xml">
-            <tree fonts="bold:needaction_pending==True" colors="grey:state == 'cancel';red:date_end and date_end&lt;current_date;black:date_end&gt;current_date;" string="Purchase Requisition">
-                <field name="needaction_pending" invisible="1"/>
+            <tree fonts="bold:message_unread==True" colors="grey:state == 'cancel';red:date_end and date_end&lt;current_date;black:date_end&gt;current_date;" string="Purchase Requisition">
+                <field name="message_unread" invisible="1"/>
                 <field name="name"/>
                 <field name="date_start"/>
                 <field name="user_id"/>
index 181bd81..49ebe60 100644 (file)
     </div>
 </div>
             ]]></field>
-            <field name="body_text"><![CDATA[
-Hello${object.partner_id.name and ' ' or ''}${object.partner_id.name or ''},
-
-Here is your ${object.state in ('draft', 'sent') and 'quotation' or 'order confirmation'} from ${object.company_id.name}:
-       | Order number: *${object.name}*
-       | Order total: *${object.amount_total} ${object.pricelist_id.currency_id.name}*
-       | Order date: ${object.date_order}
-       % if object.origin:
-       | Order reference: ${object.origin}
-       % endif
-       % if object.client_order_ref:
-       | Your reference: ${object.client_order_ref}<br />
-       % endif
-       | Your contact: ${object.user_id.name} ${object.user_id.email and '<%s>'%(object.user_id.email) or ''}
-
-You can view the ${object.state in ('draft', 'sent') and 'quotation' or 'order confirmation'}, download it and even pay online using the following link:
-    ${ctx.get('edi_web_url_view') or 'n/a'}
-
-% if object.order_policy in ('prepaid','manual') and object.company_id.paypal_account and object.state not in ('draft', 'sent'):
-<%
-comp_name = quote(object.company_id.name)
-order_name = quote(object.name)
-paypal_account = quote(object.company_id.paypal_account)
-order_amount = quote(str(object.amount_total))
-cur_name = quote(object.pricelist_id.currency_id.name)
-paypal_url = "https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=%s&item_name=%s%%20Order%%20%s&invoice=%s&amount=%s" \
-             "&currency_code=%s&button_subtype=services&no_note=1&bn=OpenERP_Order_PayNow_%s" % \
-             (paypal_account,comp_name,order_name,order_name,order_amount,cur_name,cur_name)
-%>
-It is also possible to directly pay with Paypal:
-    ${paypal_url}
-% endif
-
-If you have any question, do not hesitate to contact us.
-
-
-Thank you for choosing ${object.company_id.name}!
-
-
---
-${object.user_id.name} ${object.user_id.email and '<%s>'%(object.user_id.email) or ''}
-${object.company_id.name}
-% if object.company_id.street:
-${object.company_id.street or ''}
-% endif
-% if object.company_id.street2:
-${object.company_id.street2}
-% endif
-% if object.company_id.city or object.company_id.zip:
-${object.company_id.zip or ''} ${object.company_id.city or ''}
-% endif
-% if object.company_id.country_id:
-${object.company_id.state_id and ('%s, ' % object.company_id.state_id.name) or ''} ${object.company_id.country_id.name or ''}
-% endif
-% if object.company_id.phone:
-Phone: ${object.company_id.phone}
-% endif
-% if object.company_id.website:
-${object.company_id.website or ''}
-% endif
-            ]]></field>
         </record>
     </data>
 </openerp>
index 2c477d1..393b3e5 100644 (file)
@@ -1112,7 +1112,7 @@ msgid "Document of the move to the output or to the customer."
 msgstr ""
 
 #. module: sale
-#: model:email.template,body_text:sale.email_template_edi_sale
+#: model:email.template,body:sale.email_template_edi_sale
 msgid ""
 "\n"
 "Hello${object.partner_order_id.name and ' ' or ''}${object.partner_order_id."
index c525ba0..6a6678d 100644 (file)
@@ -48,7 +48,7 @@ sale_shop()
 
 class sale_order(osv.osv):
     _name = "sale.order"
-    _inherit = ['ir.needaction_mixin', 'mail.thread']
+    _inherit = ['mail.thread', 'ir.needaction_mixin']
     _description = "Sales Order"
 
 
@@ -1022,24 +1022,20 @@ class sale_order(osv.osv):
     # OpenChatter methods and notifications
     # ------------------------------------------------
 
-    def get_needaction_user_ids(self, cr, uid, ids, context=None):
-        result = super(sale_order, self).get_needaction_user_ids(cr, uid, ids, context=context)
-        for obj in self.browse(cr, uid, ids, context=context):
-            if (obj.state == 'manual' or obj.state == 'progress'):
-                result[obj.id].append(obj.user_id.id)
-        return result
+    def needaction_domain_get(self, cr, uid, ids, context=None):
+        return [('state', '=', 'draft'), ('user_id','=',uid)]
 
     def create_send_note(self, cr, uid, ids, context=None):
         for obj in self.browse(cr, uid, ids, context=context):
-            self.message_append_note(cr, uid, [obj.id], body=_("Quotation for <em>%s</em> has been <b>created</b>.") % (obj.partner_id.name), context=context)
+            self.message_post(cr, uid, [obj.id], body=_("Quotation for <em>%s</em> <b>created</b>.") % (obj.partner_id.name), context=context)
 
     def confirm_send_note(self, cr, uid, ids, context=None):
         for obj in self.browse(cr, uid, ids, context=context):
-            self.message_append_note(cr, uid, [obj.id], body=_("Quotation for <em>%s</em> <b>converted</b> to Sale Order of %s %s.") % (obj.partner_id.name, obj.amount_total, obj.pricelist_id.currency_id.symbol), context=context)
+            self.message_post(cr, uid, [obj.id], body=_("Quotation for <em>%s</em> <b>converted</b> to Sale Order of %s %s.") % (obj.partner_id.name, obj.amount_total, obj.pricelist_id.currency_id.symbol), context=context)
 
     def cancel_send_note(self, cr, uid, ids, context=None):
         for obj in self.browse(cr, uid, ids, context=context):
-            self.message_append_note(cr, uid, [obj.id], body=_("Sale Order for <em>%s</em> <b>cancelled</b>.") % (obj.partner_id.name), context=context)
+            self.message_post(cr, uid, [obj.id], body=_("Sale Order for <em>%s</em> <b>cancelled</b>.") % (obj.partner_id.name), context=context)
 
     def delivery_send_note(self, cr, uid, ids, picking_id, context=None):
         for order in self.browse(cr, uid, ids, context=context):
@@ -1048,21 +1044,21 @@ class sale_order(osv.osv):
                 # convert it to the user TZ and re-render it with %Z to add the timezone
                 picking_datetime = fields.DT.datetime.strptime(picking.min_date, DEFAULT_SERVER_DATETIME_FORMAT)
                 picking_date_str = fields.datetime.context_timestamp(cr, uid, picking_datetime, context=context).strftime(DATETIME_FORMATS_MAP['%+'] + " (%Z)")
-                self.message_append_note(cr, uid, [order.id], body=_("Delivery Order <em>%s</em> <b>scheduled</b> for %s.") % (picking.name, picking_date_str), context=context)
+                self.message_post(cr, uid, [order.id], body=_("Delivery Order <em>%s</em> <b>scheduled</b> for %s.") % (picking.name, picking_date_str), context=context)
 
     def delivery_end_send_note(self, cr, uid, ids, context=None):
-        self.message_append_note(cr, uid, ids, body=_("Order <b>delivered</b>."), context=context)
+        self.message_post(cr, uid, ids, body=_("Order <b>delivered</b>."), context=context)
 
     def invoice_paid_send_note(self, cr, uid, ids, context=None):
-        self.message_append_note(cr, uid, ids, body=_("Invoice <b>paid</b>."), context=context)
+        self.message_post(cr, uid, ids, body=_("Invoice <b>paid</b>."), context=context)
 
     def invoice_send_note(self, cr, uid, ids, invoice_id, context=None):
         for order in self.browse(cr, uid, ids, context=context):
             for invoice in (inv for inv in order.invoice_ids if inv.id == invoice_id):
-                self.message_append_note(cr, uid, [order.id], body=_("Draft Invoice of %s %s <b>waiting for validation</b>.") % (invoice.amount_total, invoice.currency_id.symbol), context=context)
+                self.message_post(cr, uid, [order.id], body=_("Draft Invoice of %s %s <b>waiting for validation</b>.") % (invoice.amount_total, invoice.currency_id.symbol), context=context)
 
     def action_cancel_draft_send_note(self, cr, uid, ids, context=None):
-        return self.message_append_note(cr, uid, ids, body='Sale order has been set in draft.', context=context)
+        return self.message_post(cr, uid, ids, body=_('Sale order set to draft.'), context=context)
 
 
 sale_order()
index bbee9ca..a2ef6dd 100644 (file)
         <function eval="('default',False,'shop_id', [('sale.order', False)], sale_shop_1, True, False, False, False, True)" id="sale_default_set" model="ir.values" name="set"/>
 
         <!-- notify all employees of module installation -->
-        <function model="mail.group" name="message_append_note">
-            <!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
-            <value eval="[ref('mail.group_all_employees')]"/>
-            <value>The Sales Management application has been installed.</value>
-            <value>This modules allows you to create and send easily quotations and process your sales orders; from the delivery to the invoicing.
+        <record model="mail.message" id="module_install_notification">
+            <field name="model">mail.group</field>
+            <field name="res_id" ref="mail.group_all_employees"/>
+            <field name="type">notification</field>
+            <field name="subject">Sales Management application installed!</field>
+            <field name="body">This application lets you create and send quotations and process your sales orders; from delivery to invoicing.
 
-If you need to manage your sales pipeline (leads, opportunities, phonecalls), you can install the module &lt;i&gt;CRM&lt;/i&gt; from the top menu Settings.</value>
-        </function>
+If you need to manage your sales pipeline (leads, opportunities, phonecalls), the &lt;i&gt;CRM&lt;/i&gt; application may be useful. Use the Settings menu to install it.</field>
+        </record>
     </data>
 </openerp>
index 192d734..c8a3bb7 100644 (file)
         <record id="message_sale_1" model="mail.message">
             <field name="model">sale.order</field>
             <field name="res_id" ref="sale_order_2"/>
-            <field name="content_subtype">plain</field>
-            <field name="body_text">Hi,
+            <field name="body">Hi,
 I have a confusion for pricing of Services, I have heard there is a discount above 25 hours.
 Can you clarify please?</field>
             <field name="type">comment</field>
-            <field name="user_id" ref="base.user_demo"/>
+            <field name="author_id" ref="base.partner_demo"/>
         </record>
 
         <record id="message_sale_2" model="mail.message">
             <field name="model">sale.order</field>
             <field name="res_id" ref="sale_order_2"/>
             <field name="parent_id" ref="message_sale_1"/>
-            <field name="content_subtype">plain</field>
-            <field name="body_text">Hello, 
+            <field name="body">Hello, 
 Sorry but that scheme is not available for now,
 We would like to know if you confirm the quotation with pricing we sent to you.
 Thanks,
 Sales Department</field>
             <field name="type">comment</field>
-            <field name="user_id" ref="base.user_root"/>
+            <field name="author_id" ref="base.partner_root"/>
         </record>
 
         <record id="message_sale_3" model="mail.message">
             <field name="model">sale.order</field>
             <field name="res_id" ref="sale_order_2"/>
             <field name="parent_id" ref="message_sale_2"/>
-            <field name="content_subtype">plain</field>
-            <field name="body_text">Ok, fine, we will intimate you after discussing with our team.</field>
+            <field name="body">Ok, fine, we will intimate you after discussing with our team.</field>
             <field name="type">comment</field>
-            <field name="user_id" ref="base.user_demo"/>
+            <field name="author_id" ref="base.partner_demo"/>
         </record>
         <!-- sale advance demo.. -->
         <!-- Demo Data for Product -->
index 95af680..2c2acff 100644 (file)
             <field name="model">sale.order</field>
             <field name="priority">2</field>
             <field name="arch" type="xml">
-                <tree string="Sales Orders" fonts="bold:needaction_pending==True" colors="grey:state=='cancel';blue:state in ('waiting_date','manual');red:state in ('invoice_except','shipping_except')">
-                    <field name="needaction_pending" invisible="1"/>
+                <tree string="Sales Orders" fonts="bold:message_unread==True" colors="grey:state=='cancel';blue:state in ('waiting_date','manual');red:state in ('invoice_except','shipping_except')">
+                    <field name="message_unread" invisible="1"/>
                     <field name="name"/>
                     <field name="date_order"/>
                     <field name="partner_id"/>
             <field name="arch" type="xml">
                 <search string="Search Sales Order">
                     <field name="name" string="Sales Order" filter_domain="['|',('name','ilike',self),('client_order_ref','ilike',self)]"/>
-                    <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="needaction_pending" domain="[('needaction_pending','=',True)]"/>
+                    <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/>
                     <separator/>
                     <filter icon="terp-document-new" string="Quotations" name="draft" domain="[('state','in',('draft','sent'))]" help="Sales Order that haven't yet been confirmed"/>
                     <filter icon="terp-check" string="Sales" name="sales" domain="[('state','in',('manual','progress'))]"/>
index 6ca9ac8..2597c1d 100644 (file)
@@ -106,7 +106,7 @@ class crm_make_sale(osv.osv_memory):
                 case_obj.write(cr, uid, [case.id], {'ref': 'sale.order,%s' % new_id})
                 new_ids.append(new_id)
                 message = _("Opportunity has been <b>converted</b> to the quotation <em>%s</em>.") % (sale_order.name)
-                case.message_append_note(body=message)
+                case.message_post(body=message)
             if make.close:
                 case_obj.case_close(cr, uid, data)
             if not new_ids:
index 204ca1e..375c33b 100644 (file)
@@ -775,8 +775,8 @@ class share_wizard(osv.TransientModel):
             if res_id <= 0:
                 raise osv.except_osv(_('Record id not found'), _('The share engine has not been able to fetch a record_id for your invitation.'))
             self.pool.get(model.model).message_subscribe(cr, uid, [res_id], new_ids + existing_ids, context=context)
-            self.send_invite_email(cr, uid, wizard_data, context=context)
-            self.send_invite_note(cr, uid, model.model, res_id, wizard_data, context=context)
+            # self.send_invite_email(cr, uid, wizard_data, context=context)
+            # self.send_invite_note(cr, uid, model.model, res_id, wizard_data, context=context)
         
         # CLOSE
         #  A. Not invite: as before
@@ -820,9 +820,10 @@ class share_wizard(osv.TransientModel):
             elif tmp_idx == len(wizard_data.result_line_ids)-2:
                 body += ' and'
         body += '.'
-        return self.pool.get(model_name).message_append_note(cr, uid, [res_id], _('System Notification'), body, context=context)
+        return self.pool.get(model_name).message_post(cr, uid, [res_id], body=body, context=context)
     
     def send_invite_email(self, cr, uid, wizard_data, context=None):
+        # TDE Note: not updated because will disappear
         message_obj = self.pool.get('mail.message')
         notification_obj = self.pool.get('mail.notification')
         user = self.pool.get('res.users').browse(cr, UID_ROOT, uid)
@@ -855,13 +856,13 @@ class share_wizard(osv.TransientModel):
     
     def send_emails(self, cr, uid, wizard_data, context=None):
         _logger.info('Sending share notifications by email...')
-        mail_message = self.pool.get('mail.message')
+        mail_mail = self.pool.get('mail.mail')
         user = self.pool.get('res.users').browse(cr, UID_ROOT, uid)
         if not user.email:
             raise osv.except_osv(_('Email required'), _('The current user must have an email address configured in User Preferences to be able to send outgoing emails.'))
         
         # TODO: also send an HTML version of this mail
-        msg_ids = []
+        mail_ids = []
         for result_line in wizard_data.result_line_ids:
             email_to = result_line.user_id.email
             if not email_to:
@@ -884,10 +885,14 @@ class share_wizard(osv.TransientModel):
             body += "--\n"
             body += _("OpenERP is a powerful and user-friendly suite of Business Applications (CRM, Sales, HR, etc.)\n"
                       "It is open source and can be found on http://www.openerp.com.")
-            msg_ids.append(mail_message.schedule_with_attach(cr, uid, user.email, [email_to], subject, body, model='share.wizard', context=context))
+            mail_ids.append(mail_mail.create(cr, uid, {
+                    'email_from': user.email,
+                    'email_to': email_to,
+                    'subject': subject,
+                    'body_html': '<pre>%s</pre>' % body}, context=context))
         # force direct delivery, as users expect instant notification
-        mail_message.send(cr, uid, msg_ids, context=context)
-        _logger.info('%d share notification(s) sent.', len(msg_ids))
+        mail_mail.send(cr, uid, mail_ids, context=context)
+        _logger.info('%d share notification(s) sent.', len(mail_ids))
 
     def onchange_embed_options(self, cr, uid, ids, opt_title, opt_search, context=None):
         wizard = self.browse(cr, uid, ids[0], context)
index 7f0a744..006af58 100644 (file)
@@ -1379,13 +1379,13 @@ class stock_picking(osv.osv):
 
     def create_send_note(self, cr, uid, ids, context=None):
         for obj in self.browse(cr, uid, ids, context=context):
-            self.message_append_note(cr, uid, [obj.id], body=_("%s has been <b>created</b>.") % (self._get_document_type(obj.type)), context=context)
+            self.message_post(cr, uid, [obj.id], body=_("%s has been <b>created</b>.") % (self._get_document_type(obj.type)), context=context)
 
     def scrap_send_note(self, cr, uid, ids, quantity, uom, name, context=None):
-        return self.message_append_note(cr, uid, ids, body= _("%s %s %s has been <b>moved to</b> scrap.") % (quantity, uom, name), context=context)
+        return self.message_post(cr, uid, ids, body= _("%s %s %s has been <b>moved to</b> scrap.") % (quantity, uom, name), context=context)
 
     def back_order_send_note(self, cr, uid, ids, back_name, context=None):
-        return self.message_append_note(cr, uid, ids, body=_("Back order <em>%s</em> has been <b>created</b>.") % (back_name), context=context)
+        return self.message_post(cr, uid, ids, body=_("Back order <em>%s</em> has been <b>created</b>.") % (back_name), context=context)
 
     def ship_done_send_note(self, cr, uid, ids, context=None):
         type_dict = {
@@ -1394,11 +1394,11 @@ class stock_picking(osv.osv):
                 'internal': 'moved',
         }
         for obj in self.browse(cr, uid, ids, context=context):
-            self.message_append_note(cr, uid, [obj.id], body=_("Products have been <b>%s</b>.") % (type_dict.get(obj.type, 'move done')), context=context)
+            self.message_post(cr, uid, [obj.id], body=_("Products have been <b>%s</b>.") % (type_dict.get(obj.type, 'move done')), context=context)
 
     def ship_cancel_send_note(self, cr, uid, ids, context=None):
         for obj in self.browse(cr, uid, ids, context=context):
-            self.message_append_note(cr, uid, [obj.id], body=_("%s has been <b>cancelled</b>.") % (self._get_document_type(obj.type)), context=context)
+            self.message_post(cr, uid, [obj.id], body=_("%s has been <b>cancelled</b>.") % (self._get_document_type(obj.type)), context=context)
 
 
 stock_picking()
@@ -2542,7 +2542,7 @@ class stock_move(osv.osv):
         product_obj = self.pool.get('product.product')
         for new_move in self.browse(cr, uid, res, context=context):
             message = _("Product has been consumed with '%s' quantity.") % (new_move.product_qty)
-            product_obj.message_append_note(cr, uid, [new_move.product_id.id], body=message, context=context)
+            product_obj.message_post(cr, uid, [new_move.product_id.id], body=message, context=context)
 
         self.action_done(cr, uid, res, context=context)
 
index f4b3ab9..77c8298 100644 (file)
@@ -2,12 +2,14 @@
 <openerp>
     <data noupdate="1">
         <!-- notify all employees of module installation -->
-        <function model="mail.group" name="message_append_note">
-            <!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
-            <value eval="[ref('mail.group_all_employees')]"/>
-            <value>Module Warehouse Management has been installed</value>
-            <value>Manage your product inventoy and stock locations.  You can control your stock moves history and planning, perform stock valuation, and trace product lots upstream and downstream (based on serial numbers.)</value>
-        </function>
+        <record model="mail.message" id="module_install_notification">
+            <field name="model">mail.group</field>
+            <field name="res_id" ref="mail.group_all_employees"/>
+            <field name="type">notification</field>
+            <field name="subject">Warehouse Management application installed!</field>
+            <field name="body">Manage your product inventoy and stock locations: you can control stock moves history and planning,
+watch your stock valuation, and track production lots upstream and downstream (based on serial numbers).</field>
+        </record>
 
         <record id="stock_journal_sequence" model="ir.sequence">
             <field name="name">Stock Journal Sequence</field>
index 0cc97e1..5aa4649 100644 (file)
@@ -113,7 +113,7 @@ class stock_change_product_qty(osv.osv_memory):
         for data in self.browse(cr, uid, ids, context=context):
             location_name = location_obj.browse(cr, uid, data.location_id.id, context=context).name
             message = _("<b>Quantity has been changed</b> to <em>%s %s </em> for <em>%s</em> location.") % (data.new_quantity, data.product_id.uom_id.name, location_name)
-            prod_obj.message_append_note(cr, uid, [data.product_id.id], body=message, context=context)
+            prod_obj.message_post(cr, uid, [data.product_id.id], body=message, context=context)
 
 stock_change_product_qty()
 
index 66098b4..e2216ea 100644 (file)
@@ -420,7 +420,17 @@ class survey_question_wiz(osv.osv_memory):
                         if user_email and resp_email:
                             user_name = user_obj.browse(cr, uid, uid, context=context).name
                             mail = "Hello " + survey_data.responsible_id.name + ",\n\n " + str(user_name) + " has given the Response Of " + survey_data.title + " Survey.\nThe Response has been attached herewith.\n\n Thanks."
-                            mail_message.schedule_with_attach(cr, uid, user_email, [resp_email], "Survey Answer Of " + str(user_name) , mail, attachments=attachments, context=context)
+                            vals = {'state': 'outgoing',
+                                    'subject': "Survey Answer Of " + user_name,
+                                    'body_html': '<pre>%s</pre>' % mail,
+                                    'email_to': [resp_email],
+                                    'email_from': user_email}
+                            if attachments:
+                                vals['attachment_ids'] = [(0,0,{'name': a_name,
+                                                                'datas_fname': a_name,
+                                                                'datas': str(a_content).encode('base64')})
+                                                                for a_name, a_content in attachments]
+                            self.pool.get('mail.mail').create(cr, uid, vals, context=context)
 
                     xml_form = etree.Element('form', {'string': _('Complete Survey Answer')})
                     etree.SubElement(xml_form, 'separator', {'string': 'Complete Survey', 'colspan': "4"})
index da43995..5677a98 100644 (file)
@@ -116,7 +116,6 @@ class survey_send_invitation(osv.osv_memory):
         existing = ""
         created = ""
         error = ""
-        user_exists = False
         new_user = []
         attachments = {}
         current_sur = survey_ref.browse(cr, uid, context.get('active_id'), context=context)
@@ -150,8 +149,14 @@ class survey_send_invitation(osv.osv_memory):
                 mail = record['mail']%{'login':partner.email, 'passwd':user.password, \
                                             'name' : partner.name}
                 if record['send_mail_existing']:
-                    mail_message.schedule_with_attach(cr, uid, record['mail_from'], [partner.email] , \
-                                     record['mail_subject_existing'] , mail, context=context)
+                    vals = {
+                        'state': 'outgoing',
+                        'subject': record['mail_subject_existing'],
+                        'body_html': '<pre>%s</pre>' % mail,
+                        'email_to': partner.email,
+                        'email_from': record['mail_from'],
+                    }
+                    self.pool.get('mail.mail').create(cr, uid, vals, context=context)
                     existing+= "- %s (Login: %s,  Password: %s)\n" % (user.name, partner.email, \
                                                                       user.password)
                 continue
@@ -160,8 +165,19 @@ class survey_send_invitation(osv.osv_memory):
             out+= partner.email + ',' + passwd + '\n'
             mail= record['mail'] % {'login' : partner.email, 'passwd' : passwd, 'name' : partner.name}
             if record['send_mail']:
-                ans = mail_message.schedule_with_attach(cr, uid, record['mail_from'], [partner.email], \
-                                       record['mail_subject'], mail, attachments=attachments, context=context)
+                vals = {
+                        'state': 'outgoing',
+                        'subject': record['mail_subject'],
+                        'body_html': '<pre>%s</pre>' % mail,
+                        'email_to': partner.email,
+                        'email_from': record['mail_from'],
+                }
+                if attachments:
+                    vals['attachment_ids'] = [(0,0,{'name': a_name,
+                                                    'datas_fname': a_name,
+                                                    'datas': str(a_content).encode('base64')})
+                                                    for a_name, a_content in attachments]
+                ans = self.pool.get('mail.mail').create(cr, uid, vals, context=context)
                 if ans:
                     res_data = {'name': partner.name or _('Unknown'),
                                 'login': partner.email,