diff options
| author | Rucha (Open ERP) <rpa@tinyerp.com> | 2012-07-03 11:53:51 +0530 |
|---|---|---|
| committer | Rucha (Open ERP) <rpa@tinyerp.com> | 2012-07-03 11:53:51 +0530 |
| commit | 9e6f918bbee6f6a6ebfc7eba8cd310d2f08b638f (patch) | |
| tree | a4446727c5afbda668b896ccd075e332e0000f5e /addons | |
| parent | 2558625ebab05a78d73d1b1d9596bd0d26173e44 (diff) | |
| parent | 38d7e1298ed7bb93f1a1ff9391192a9238c7cc1f (diff) | |
[MERGE]: Merge with latest trunk-addons
bzr revid: rpa@tinyerp.com-20120703062351-tazpsjb7a3usajvf
Diffstat (limited to 'addons')
76 files changed, 1964 insertions, 891 deletions
diff --git a/addons/account/account_installer.xml b/addons/account/account_installer.xml index 73c9652f8fa..8e2526c3f97 100644 --- a/addons/account/account_installer.xml +++ b/addons/account/account_installer.xml @@ -13,8 +13,7 @@ <attribute name="string">Continue</attribute> </button> <separator string="title" position="replace"> - <group string="Select an Accounting Setup"> - <label colspan="2" string="This will automatically configure your taxes and accounts."/> + <group> <field name="charts"/> </group> <group string="Configure your Fiscal Year" groups="account.group_account_user"> diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml index 49bbe8d3a4d..e85a0002bf8 100644 --- a/addons/account/account_invoice_view.xml +++ b/addons/account/account_invoice_view.xml @@ -157,33 +157,39 @@ <field name="state" widget="statusbar" statusbar_visible="draft,open,paid" statusbar_colors='{"proforma":"blue","proforma2":"blue"}'/> </header> <sheet string="Supplier Invoice"> - <group> - <group> - <field string="Supplier" name="partner_id" + <div class="oe_title"> + <h1> + <label string="Draft Invoice" attrs="{'invisible': [('state', '<>', 'draft')]}"/> + <label for="number" class="oe_edit_only" attrs="{'invisible': [('state', '=', 'draft')]}"/> + <field name="number" class="oe_inline" attrs="{'invisible': [('state', '=', 'draft')]}"/> + </h1> + <h2> + <label for="origin" string="Concerns" class="oe_inline"/> + <field name="origin" placeholder="PO0025" class="oe_inline"/> + <label string=" from " attrs="{'invisible': [('origin', '=', False)]}" class='oe_inline'/> + <field string="Supplier" name="partner_id" class="oe_inline" on_change="onchange_partner_id(type,partner_id,date_invoice,payment_term, partner_bank_id,company_id)" context="{'default_customer': 0, 'search_default_supplier': 1, 'default_supplier': 1}" domain="[('supplier', '=', True)]"/> - <field name="fiscal_position" widget="selection"/> - </group> + </h2> + </div> + <group> <group> - <field name="number"/> <field name="date_invoice"/> - <field name="period_id" domain="[('state', '=', 'draft')]" - groups="account.group_account_user" widget="selection"/> - <field name="journal_id" on_change="onchange_journal_id(journal_id)" widget="selection" - groups="account.group_account_user"/> - <field domain="[('company_id', '=', company_id), ('type', '=', 'payable')]" - name="account_id" groups="account.group_account_user"/> - <field name="currency_id"/> + <field name="date_due"/> <field name="type" invisible="1"/> + <field name="period_id" domain="[('state', '=', 'draft')]" groups="account.group_account_user" widget="selection"/> + </group> + <group> + <field name="fiscal_position" widget="selection"/> + <field name="currency_id"/> + <field domain="[('company_id', '=', company_id), ('type', '=', 'payable')]" name="account_id" groups="account.group_account_user" invisible="1"/> </group> <group> <label for="reference"/> <div> - <field name="reference_type"/> <field name="reference" placeholder="Payment Reference"/> </div> - <field name="date_due"/> </group> </group> <notebook> @@ -196,10 +202,11 @@ <field domain="[('type','<>','view'), ('company_id', '=', parent.company_id), ('parent_id', '!=', False)]" name="account_analytic_id" groups="analytic.group_analytic_accounting"/> <field name="quantity"/> <field name="price_unit"/> - <!-- Removed if subtotal is set --> <field name="price_subtotal"/> - <field invisible="True" name="name"/> - <field invisible="True" name="uos_id"/> + <field domain="[('company_id', '=', parent.company_id), ('journal_id', '=', parent.journal_id), ('type', '<>', 'view')]" name="account_id" on_change="onchange_account_id(product_id,parent.partner_id,parent.type,parent.fiscal_position,account_id)" invisible="1"/> + <!-- Removed if subtotal is set --> + <field name="name" invisible="1"/> + <field name="uos_id" invisible="1"/> </tree> </field> <group> @@ -232,16 +239,17 @@ <field name="comment"/> </page> <page string="Other Info"> - <group col="4"> + <group> + <group> <field domain="[('partner_id', '=', partner_id)]" name="partner_bank_id" on_change="onchange_partner_bank(partner_bank_id)"/> - <field name="company_id" on_change="onchange_company_id(company_id,partner_id,type,invoice_line,currency_id)" widget="selection" groups="base.group_multi_company"/> - <newline/> <field name="payment_term" widget="selection"/> - <field name="name"/> - <newline/> - <field name="origin" placeholder="PO0025"/> + </group> + <group> <field name="user_id"/> + <field name="name" invisible="1"/> <field name="move_id" groups="account.group_account_user"/> + <field name="company_id" on_change="onchange_company_id(company_id,partner_id,type,invoice_line,currency_id)" widget="selection" groups="base.group_multi_company"/> + </group> </group> </page> <page string="Payments"> diff --git a/addons/account/account_view.xml b/addons/account/account_view.xml index 9585719803c..a89473af134 100644 --- a/addons/account/account_view.xml +++ b/addons/account/account_view.xml @@ -2357,12 +2357,11 @@ <group string="res_config_contents" position="replace"> <field name="only_one_chart_template" invisible="1"/> <field name="complete_tax_set" invisible="1"/> - <p>This will automatically configure your chart of accounts, bank accounts, taxes and journals according to the selected template.</p> <div groups="base.group_multi_company"> <label for="company_id"/> <field name="company_id" widget="selection"/> <!-- we assume that this wizard will be run only by administrators and as this field may cause problem if hidden (because of the default company of the user removed from the selection because already configured), we simply choosed to remove the group "multi company" of it --> </div> - <group string="Set Your Accounting Options"> + <group> <div attrs="{'invisible': [('only_one_chart_template','=',True)]}"> <label for="chart_template_id"/> <field name="chart_template_id" widget="selection" on_change="onchange_chart_template_id(chart_template_id)" domain="[('visible','=', True)]"/> diff --git a/addons/account/product_view.xml b/addons/account/product_view.xml index 98f9f93dfa7..8591b28b173 100644 --- a/addons/account/product_view.xml +++ b/addons/account/product_view.xml @@ -51,12 +51,12 @@ <field name="type">form</field> <field name="inherit_id" ref="product.product_category_form_view"/> <field name="arch" type="xml"> - <form position="inside"> - <group string="Accounting Properties"> + <data> + <xpath expr="/form/sheet//group[@name='account_property']" position="inside"> <field name="property_account_income_categ" domain="[('type','<>','view'),('type','<>','consolidation')]"/> <field name="property_account_expense_categ" domain="[('type','<>','view'),('type','<>','consolidation')]"/> - </group> - </form> + </xpath> + </data> </field> </record> diff --git a/addons/account_anglo_saxon/product_view.xml b/addons/account_anglo_saxon/product_view.xml index 8a79d96d1c4..b88f8f2db42 100644 --- a/addons/account_anglo_saxon/product_view.xml +++ b/addons/account_anglo_saxon/product_view.xml @@ -34,12 +34,11 @@ <field name="type">form</field> <field name="inherit_id" ref="product.product_category_form_view"/> <field name="arch" type="xml"> - <form position="inside"> - <group> - <separator string="Accounting Property" colspan="2"/> + <data> + <xpath expr="/form/sheet//group[@name='account_property']" position="inside"> <field name="property_account_creditor_price_difference_categ" domain="[('type','<>','view'),('type','<>','consolidation')]"/> - </group> - </form> + </xpath> + </data> </field> </record> diff --git a/addons/base_calendar/base_calendar_view.xml b/addons/base_calendar/base_calendar_view.xml index cb2e5391018..76bb0c24cb5 100644 --- a/addons/base_calendar/base_calendar_view.xml +++ b/addons/base_calendar/base_calendar_view.xml @@ -45,10 +45,10 @@ </group> </group> <group> - <group string="Delegrated From"> + <group string="Delegated From"> <field name="parent_ids" readonly="1" nolabel="1" /> </group> - <group string="Delegrated To" > + <group string="Delegated To" > <field name="child_ids" readonly="1" nolabel="1" /> </group> </group> diff --git a/addons/caldav/wizard/caldav_browse_view.xml b/addons/caldav/wizard/caldav_browse_view.xml index 8f06ed47ddd..29f455d764f 100644 --- a/addons/caldav/wizard/caldav_browse_view.xml +++ b/addons/caldav/wizard/caldav_browse_view.xml @@ -7,13 +7,36 @@ <field name="model">caldav.browse</field> <field name="type">form</field> <field name="arch" type="xml"> - <form string="Browse Caldav" > - <group colspan="4" width="700" height="500"> - <separator string="Browse Caldav" colspan="4"/> - <field name="url" colspan="4" /> - <separator string="Description" colspan="4"/> - <field name="description" colspan="4" nolabel="1"/> - </group> + <form string="Browse Caldav" version="7.0"> + <header> + <button special="cancel" string="_Close" icon="gtk-close"/> + </header> + <sheet> + <group> + <separator string="Browse Caldav" colspan="4"/> + <field name="url" colspan="2" /> + <separator string="Help" colspan="4"/> + <group colspan="4"><div> + 1. Webdav server that provides remote access to calendar<br/> + 2. Synchronisation of calendar using WebDAV<br/> + 3. Customize calendar event and todo attribute with any of OpenERP model<br/> + 4. Provides iCal Import/Export functionality + <br/></div></group> + <group colspan="4"><div> + To access Calendars using CalDAV clients, point them to: + http://<i>HOSTNAME</i>:<i>PORT</i>/webdav/<i>DATABASE_NAME</i>/calendars/users/<i>USERNAME</i>/c</div></group> + <group colspan="4"><div> + To access OpenERP Calendar using WebCal to remote site use the URL like: + http://<i>HOSTNAME</i>:<i>PORT</i>/webdav/<i>DATABASE_NAME</i>/Calendars/<i>CALENDAR_NAME</i>.ics</div></group> + <group colspan="4"><div> + <i> + HOSTNAME: Host on which OpenERP server(With webdav) is running<br/> + PORT : Port on which OpenERP server is running (By Default : 8069)<br/> + DATABASE_NAME: Name of database on which OpenERP Calendar is created</i> + </div></group> + + </group> + </sheet> </form> </field> </record> @@ -29,12 +52,11 @@ or <button string="Cancel" class="oe_link" special="cancel"/> </header> - <label string="Configure your openerp hostname. For example : " colspan="4"></label> - <newline/> - <label string="database.my.openerp.com or companyserver.com" colspan="4"/> - <newline/> - <separator colspan="4"/> - <field name="host_name" colspan="4" width="250" /> + <sheet> + <group> + <field name="host_name" colspan="2" width="250" placeholder="For example : database.my.openerp.com or companyserver.com"/> + </group> + </sheet> </form> </field> </record> @@ -50,11 +72,14 @@ or <button string="Cancel" class="oe_link" special="cancel"/> </header> - <separator string="" colspan="4"/> - <field name="service" colspan="4" width="250" readonly="1"/> - <field name="collection" colspan="4" width="250" invisible="1" /> - <field name="calendar" colspan="4" width="250" domain="[('collection_id','=', collection)]" readonly="1"/> - <field name="device" colspan="4" width="250" /> + <sheet> + <group> + <field name="service" readonly="1"/> + <field name="collection" invisible="1" /> + <field name="calendar" domain="[('collection_id','=', collection)]" readonly="1"/> + <field name="device"/> + </group> + </sheet> </form> </field> </record> diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 92dd6d6e837..69d390ff04d 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -273,14 +273,6 @@ class crm_lead(base_stage, osv.osv): 'color': 0, } - def get_needaction_user_ids(self, cr, uid, ids, context=None): - result = dict.fromkeys(ids, []) - for obj in self.browse(cr, uid, ids, context=context): - # salesman must perform an action when in draft mode - if obj.state == 'draft' and obj.user_id: - result[obj.id] = [obj.user_id.id] - return result - def create(self, cr, uid, vals, context=None): obj_id = super(crm_lead, self).create(cr, uid, vals, context) self.create_send_note(cr, uid, [obj_id], context=context) @@ -770,64 +762,6 @@ class crm_lead(base_stage, osv.osv): 'type': 'ir.actions.act_window', } - - def message_new(self, cr, uid, msg, custom_values=None, context=None): - """Automatically calls when new email message arrives""" - res_id = super(crm_lead, self).message_new(cr, uid, msg, custom_values=custom_values, context=context) - subject = msg.get('subject') or _("No Subject") - body = msg.get('body_text') - - msg_from = msg.get('from') - priority = msg.get('priority') - vals = { - 'name': subject, - 'email_from': msg_from, - 'email_cc': msg.get('cc'), - 'description': body, - 'user_id': False, - } - if priority: - vals['priority'] = priority - vals.update(self.message_partner_by_email(cr, uid, msg.get('from', False))) - self.write(cr, uid, [res_id], vals, context) - return res_id - - def message_update(self, cr, uid, ids, msg, vals=None, default_act='pending', context=None): - if isinstance(ids, (str, int, long)): - ids = [ids] - if vals == None: - vals = {} - super(crm_lead, self).message_update(cr, uid, ids, msg, context=context) - - if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES): - vals['priority'] = msg.get('priority') - maps = { - 'cost':'planned_cost', - 'revenue': 'planned_revenue', - 'probability':'probability' - } - vls = {} - for line in msg['body_text'].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()) - vls[key] = res.group(2).lower() - vals.update(vls) - - # Unfortunately the API is based on lists - # but we want to update the state based on the - # previous state, so we have to loop: - for case in self.browse(cr, uid, ids, context=context): - values = dict(vals) - if case.state in CRM_LEAD_PENDING_STATES: - #re-open - values.update(state=crm.AVAILABLE_STATES[1][0]) - if not case.date_open: - values['date_open'] = time.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT) - res = self.write(cr, uid, [case.id], values, context=context) - return res - def action_makeMeeting(self, cr, uid, ids, context=None): """ This opens Meeting's calendar view to schedule meeting on current Opportunity @@ -882,19 +816,66 @@ class crm_lead(base_stage, osv.osv): if stage.on_change: vals['probability'] = stage.probability return super(crm_lead,self).write(cr, uid, ids, vals, context) - + + # ---------------------------------------- + # Mail Gateway + # ---------------------------------------- + + 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': msg.get('subject') or _("No Subject"), + 'description': msg.get('body_text'), + '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): + """ Overrides mail_thread message_update that is called by the mailgateway + through message_process. + This method updates the document according to the email. + """ + if isinstance(ids, (str, int, long)): + ids = [ids] + if update_vals is None: update_vals = {} + + if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES): + vals['priority'] = msg.get('priority') + maps = { + 'cost':'planned_cost', + 'revenue': 'planned_revenue', + 'probability':'probability', + } + for line in msg.get('body_text', '').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() + + return super(crm_lead, self).message_update(cr, uid, ids, msg, update_vals=update_vals, context=context) + # ---------------------------------------- # OpenChatter methods and notifications # ---------------------------------------- def message_get_subscribers(self, cr, uid, ids, context=None): - sub_ids = self.message_get_subscribers_ids(cr, uid, ids, context=context) - # add salesman to the subscribers + """ Override to add the salesman. """ + user_ids = super(crm_lead, self).message_get_subscribers(cr, uid, ids, context=context) for obj in self.browse(cr, uid, ids, context=context): - if obj.user_id: - sub_ids.append(obj.user_id.id) - return self.pool.get('res.users').read(cr, uid, sub_ids, context=context) - + if obj.user_id and not obj.user_id.id in user_ids: + user_ids.append(obj.user_id.id) + return user_ids + 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] diff --git a/addons/crm/crm_lead_demo.xml b/addons/crm/crm_lead_demo.xml index d6c3cdd82cd..a56bbdb88c1 100644 --- a/addons/crm/crm_lead_demo.xml +++ b/addons/crm/crm_lead_demo.xml @@ -1,7 +1,10 @@ <?xml version="1.0"?> <openerp> - <data> - <!-- Demo Leads --> + <data noupdate="1"> + <!-- + Demo Leads + --> + <record id="crm_case_itisatelesalescampaign0" model="crm.lead"> <field name="type_id" ref="crm.type_lead1"/> <field eval="'3'" name="priority"/> @@ -21,7 +24,6 @@ <field eval="'Plan to Attend a Training'" name="name"/> <field eval="'0033 769 703-274'" name="phone"/> </record> - <record id="crm_case_electonicgoodsdealer0" model="crm.lead"> <field name="type_id" ref="crm.type_lead7"/> <field eval="'4'" name="priority"/> @@ -76,7 +78,6 @@ <field eval="'(514) 698-4118'" name="phone"/> <field eval="'hmc@thgascompany.com'" name="email_from"/> </record> - <record id="crm_case_itdeveloper0" model="crm.lead"> <field name="type_id" ref="crm.type_lead4"/> <field eval="'3'" name="priority"/> @@ -96,7 +97,6 @@ <field eval="'(855) 924-4364'" name="phone"/> <field eval="'helle@stonageit.be'" name="email_from"/> </record> - <record id="crm_case_mgroperations0" model="crm.lead"> <field eval="1" name="active"/> <field name="type_id" ref="crm.type_lead3"/> @@ -209,10 +209,21 @@ <!-- Call Function to Cancel the leads (set as Dead) --> <function model="crm.lead" name="case_cancel" - eval="[ref('crm_case_company_partnership0'), ref('crm_case_vpoperations0'), ref('crm_case_developingwebapplications0'), ref('crm_case_webvisitor0')], {'install_mode': True}" + eval="[ ref('crm_case_company_partnership0'), ref('crm_case_vpoperations0'), + ref('crm_case_developingwebapplications0'), ref('crm_case_webvisitor0')], + {'install_mode': True}" + /> + <!-- Call Function to set the leads as Unread --> + <function model="crm.lead" name="message_mark_as_unread" + eval="[ ref('crm_case_itisatelesalescampaign0'), ref('crm_case_electonicgoodsdealer0'), + ref('crm_case_itdeveloper0'), ref('crm_case_employee0')], {}" /> - <!-- Demo Opportunities --> + + <!-- + Demo Opportunities + --> + <record id="crm_case_construstazunits0" model="crm.lead"> <field eval="60" name="probability"/> <field eval="1" name="active"/> @@ -398,7 +409,7 @@ <field name="section_id" ref="crm.section_sales_department"/> <field name="categ_id" ref="crm.categ_oppor5"/> <field name="stage_id" ref="crm.stage_lead5"/> - <field eval="'Need new design for my website'" name="name"/> + <field eval="'Need a new design for my website'" name="name"/> <field eval="time.strftime('%Y-05-01')" name="date_action"/> <field eval="time.strftime('%Y-06-30')" name="date_deadline"/> <field eval="'info@opensides.be'" name="email_from"/> @@ -469,7 +480,7 @@ <field name="section_id" ref="crm.section_sales_department"/> <field name="categ_id" ref="crm.categ_oppor1"/> <field name="stage_id" ref="crm.stage_lead4"/> - <field eval="'Need customize the solution'" name="name"/> + <field eval="'Need to customize the solution'" name="name"/> <field eval="'Conf call with technical service'" name="title_action"/> <field name="partner_name">Thymbra</field> <field name="street">Palermo, Capital Federal</field> @@ -522,5 +533,12 @@ <field name="stage_id" ref="crm.stage_lead1"/> <field eval="'Need more info about your pc2'" name="name"/> </record> + + <!-- Call Function to set the opportunities as Unread --> + <function model="crm.lead" name="message_mark_as_unread" + eval="[ ref('crm_case_rdroundfundingunits25'), ref('crm_case_unifliege'), + ref('crm_case_ericdubois4'), ref('crm_case_abcfuelcounits0')], {}" + /> + </data> </openerp> diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml index a2f3c30c611..629d2f43446 100644 --- a/addons/crm/crm_lead_view.xml +++ b/addons/crm/crm_lead_view.xml @@ -2,6 +2,43 @@ <openerp> <data> + <!-- Read/Unread actions --> + <record id="actions_server_crm_lead_unread" model="ir.actions.server"> + <field name="name">Mark unread</field> + <field name="condition">True</field> + <field name="type">ir.actions.server</field> + <field name="model_id" ref="model_crm_lead"/> + <field name="state">code</field> + <field name="code">self.message_check_and_set_unread(cr, uid, context.get('active_ids'), context=context)</field> + </record> + <record id="action_crm_lead_unread" model="ir.values"> + <field name="name">action_crm_lead_unread</field> + <field name="action_id" ref="actions_server_crm_lead_unread"/> + <field name="value" eval="'ir.actions.server,' + str(ref('actions_server_crm_lead_unread'))" /> + <field name="key">action</field> + <field name="model_id" ref="model_crm_lead" /> + <field name="model">crm.lead</field> + <field name="key2">client_action_multi</field> + </record> + + <record id="actions_server_crm_lead_read" model="ir.actions.server"> + <field name="name">Mark read</field> + <field name="condition">True</field> + <field name="type">ir.actions.server</field> + <field name="model_id" ref="model_crm_lead"/> + <field name="state">code</field> + <field name="code">self.message_check_and_set_read(cr, uid, context.get('active_ids'), context=context)</field> + </record> + <record id="action_crm_lead_read" model="ir.values"> + <field name="name">action_crm_lead_read</field> + <field name="action_id" ref="actions_server_crm_lead_read"/> + <field name="value" eval="'ir.actions.server,' + str(ref('actions_server_crm_lead_read'))" /> + <field name="key">action</field> + <field name="model_id" ref="model_crm_lead" /> + <field name="model">crm.lead</field> + <field name="key2">client_action_multi</field> + </record> + <!-- CRM CASE STAGE --> @@ -172,9 +209,9 @@ </page> </notebook> </sheet> - <div class="oe_bottom"> + <footer> <field name="message_ids" widget="ThreadView"/> - </div> + </footer> </form> </field> </record> @@ -185,8 +222,7 @@ <field name="model">crm.lead</field> <field name="type">tree</field> <field name="arch" type="xml"> - <tree string="Leads" fonts="bold:needaction_pending==True" colors="blue:state=='pending';grey:state in ('cancel', 'done')"> - <field name="needaction_pending" invisible="1"/> + <tree string="Leads" fonts="bold:needaction_pending==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"/> @@ -202,6 +238,7 @@ <field name="referred" invisible="1"/> <field name="channel_id" invisible="1"/> <field name="subjects" invisible="1"/> + <field name="needaction_pending" invisible="1"/> </tree> </field> </record> @@ -235,6 +272,8 @@ <field name="user_email"/> <field name="user_id"/> <field name="partner_address_email"/> + <field name="message_summary"/> + <field name="needaction_pending"/> <templates> <t t-name="lead_details"> <ul class="oe_kanban_tooltip"> @@ -288,6 +327,8 @@ <img t-att-src="kanban_image('res.users', 'avatar', record.user_id.raw_value[0])" 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> <div class="oe_clear"></div> @@ -313,6 +354,10 @@ <field name="type">search</field> <field name="arch" type="xml"> <search string="Search Leads"> + <filter icon="terp-mail-message-new" + string="Inbox" help="Unread messages" + name="needaction_pending" + domain="[('needaction_pending','=',True)]"/> <field name="name" string="Lead / Customer" filter_domain="['|','|',('partner_name','ilike',self),('email_from','ilike',self),('name','ilike',self)]"/> <!-- subjects is not set as store=True so, it is placed outside filter_domain--> <field name="subjects"/> @@ -327,10 +372,6 @@ string="Open" name="open" domain="[('state','=','open')]"/> - <filter icon="terp-gtk-media-pause" - string="Pending" - name="pending" - domain="[('state','=','pending')]"/> <filter string="Unassigned Leads" icon="terp-personal-" domain="[('user_id','=', False)]" @@ -424,7 +465,6 @@ <div class="oe_title"> <label for="name" class="oe_edit_only"/> <h1><field name="name"/></h1> - <label for="planned_revenue" class="oe_edit_only"/> <h2> <field name="planned_revenue" class="oe_inline"/> @@ -535,8 +575,7 @@ <field name="model">crm.lead</field> <field name="type">tree</field> <field name="arch" type="xml"> - <tree string="Opportunities" fonts="bold:needaction_pending==True" colors="blue:state=='pending' and not(date_deadline and (date_deadline < current_date));gray:state in ('cancel', 'done');red:date_deadline and (date_deadline < current_date)"> - <field name="needaction_pending" invisible="1"/> + <tree string="Opportunities" fonts="bold:needaction_pending==True" colors="gray:state in ('cancel', 'done');red:date_deadline and (date_deadline < current_date)"> <field name="date_deadline" invisible="1"/> <field name="create_date" groups="base.group_no_one"/> <field name="name" string="Opportunity"/> @@ -555,6 +594,7 @@ <field name="priority" invisible="1"/> <field name="categ_id" invisible="1"/> <field name="state" groups="base.group_no_one"/> + <field name="needaction_pending" invisible="1"/> </tree> </field> </record> @@ -567,21 +607,21 @@ <field name="type">search</field> <field name="arch" type="xml"> <search string="Search Opportunities"> + <filter icon="terp-mail-message-new" + string="Inbox" help="Unread messages" + name="needaction_pending" + domain="[('needaction_pending','=',True)]"/> <field name="name" string="Opportunity / Customer" filter_domain="['|','|','|',('partner_id','ilike',self),('partner_name','ilike',self),('email_from','ilike',self),('name', 'ilike', self)]"/> <separator orientation="vertical"/> <filter icon="terp-check" - string="New" help="New Opportunities" + string="New" help="New Opportunities" name="new" domain="[('state','=','draft')]"/> <filter icon="terp-camera_test" - string="Open" help="Open Opportunities" + string="Open" help="Open Opportunities" name="open" domain="[('state','=','open')]"/> - <filter icon="terp-gtk-media-pause" - string="Pending" help="Pending Opportunities" - name="pending" - domain="[('state','=','pending')]"/> <filter string="Unassigned Opportunities" icon="terp-personal-" domain="[('user_id','=', False)]" diff --git a/addons/crm/crm_meeting.py b/addons/crm/crm_meeting.py index 1e21d535c55..37f03d80ec4 100644 --- a/addons/crm/crm_meeting.py +++ b/addons/crm/crm_meeting.py @@ -76,13 +76,6 @@ class crm_meeting(base_state, osv.Model): self.create_send_note(cr, uid, [obj_id], context=context) return obj_id - def get_needaction_user_ids(self, cr, uid, ids, context=None): - result = dict.fromkeys(ids, []) - for obj in self.browse(cr, uid, ids, context=context): - if (obj.state == 'draft' and obj.user_id): - result[obj.id] = [obj.user_id.id] - return result - def case_open(self, cr, uid, ids, context=None): """ Confirms meeting """ res = super(crm_meeting, self).case_open(cr, uid, ids, context) diff --git a/addons/crm/crm_meeting_view.xml b/addons/crm/crm_meeting_view.xml index 21c34ff4e9b..5473b727ea3 100644 --- a/addons/crm/crm_meeting_view.xml +++ b/addons/crm/crm_meeting_view.xml @@ -2,6 +2,43 @@ <openerp> <data> + <!-- Read/Unread actions --> + <record id="actions_server_crm_meeting_unread" model="ir.actions.server"> + <field name="name">Mark unread</field> + <field name="condition">True</field> + <field name="type">ir.actions.server</field> + <field name="model_id" ref="model_crm_meeting"/> + <field name="state">code</field> + <field name="code">self.message_check_and_set_unread(cr, uid, context.get('active_ids'), context=context)</field> + </record> + <record id="action_crm_meeting_unread" model="ir.values"> + <field name="name">action_crm_meeting_unread</field> + <field name="action_id" ref="actions_server_crm_meeting_unread"/> + <field name="value" eval="'ir.actions.server,' + str(ref('actions_server_crm_meeting_unread'))" /> + <field name="key">action</field> + <field name="model_id" ref="model_crm_meeting" /> + <field name="model">crm.meeting</field> + <field name="key2">client_action_multi</field> + </record> + + <record id="actions_server_crm_meeting_read" model="ir.actions.server"> + <field name="name">Mark read</field> + <field name="condition">True</field> + <field name="type">ir.actions.server</field> + <field name="model_id" ref="model_crm_meeting"/> + <field name="state">code</field> + <field name="code">self.message_check_and_set_read(cr, uid, context.get('active_ids'), context=context)</field> + </record> + <record id="action_crm_meeting_read" model="ir.values"> + <field name="name">action_crm_meeting_read</field> + <field name="action_id" ref="actions_server_crm_meeting_read"/> + <field name="value" eval="'ir.actions.server,' + str(ref('actions_server_crm_meeting_read'))" /> + <field name="key">action</field> + <field name="model_id" ref="model_crm_meeting" /> + <field name="model">crm.meeting</field> + <field name="key2">client_action_multi</field> + </record> + <!-- CRM Meetings Categories Form View --> <record id="crm_meeting_categ_action" model="ir.actions.act_window"> @@ -47,24 +84,26 @@ <h1> <field name="name"/> </h1> + </div> + <group> <h2> - At <field name="location" class="oe_inline" /> + <label for="date" string="Starting at"/> + <field name="date" required="1" class="oe_inline"/> + <label string="in" class="oe_inline" attrs="{'invisible': [('location', '=', False)]}"/> <field name="location" placeholder="Specify the location here" class="oe_inline"/> </h2> - </div> - <h2> - <label for="duration" string="Duration" /> - <field name="duration" widget="float_time" - on_change="onchange_dates(date,duration,False,allday)" - class="oe_inline"/> from - <field name="date" - string="Start Date" - required="1" - class="oe_edit_only oe_inline"/> to - <field name="date_deadline" - string="End Date" required="1" - on_change="onchange_dates(date,False,date_deadline)" - class="oe_inline"/> - </h2> + </group> + <group> + <h2> + <label for="duration" string="Duration" /> + <field name="duration" widget="float_time" + on_change="onchange_dates(date,duration,False,allday)" + class="oe_inline"/> ( + <field name="date_deadline" + string="End Date" required="1" + on_change="onchange_dates(date,False,date_deadline)" + class="oe_inline"/> ) + </h2> + </group> <group colspan="4" col="4"> <group colspan="2"> <field name="user_id" /> @@ -234,7 +273,6 @@ <field name="arch" type="xml"> <tree string="Meetings" fonts="bold:needaction_pending==True" colors="red:state=='open';black:state in ('draft', 'cancel','done','pending')"> - <field name="needaction_pending" invisible="1"/> <field name="name" string="Subject" /> <field name="user_id"/> <field name="date"/> @@ -243,6 +281,7 @@ <field name="partner_id" string="Partner" /> <field name="location" /> <field name="categ_id"/> + <field name="needaction_pending" invisible="1"/> </tree> </field> </record> @@ -284,6 +323,11 @@ <field name="arch" type="xml"> <search string="Search Meetings"> <group> + <filter icon="terp-mail-message-new" + string="Inbox" help="Unread messages" + name="needaction_pending" + domain="[('needaction_pending','=',True)]"/> + <separator orientation="vertical"/> <field name="name" string="Meeting / Partner" filter_domain="['|',('name','ilike',self),('partner_id','ilike', self)]"/> <separator orientation="vertical"/> diff --git a/addons/crm/crm_phonecall.py b/addons/crm/crm_phonecall.py index 9418b05b9ea..13991245eda 100644 --- a/addons/crm/crm_phonecall.py +++ b/addons/crm/crm_phonecall.py @@ -288,14 +288,6 @@ class crm_phonecall(base_state, osv.osv): # ---------------------------------------- # OpenChatter # ---------------------------------------- - - def get_needaction_user_ids(self, cr, uid, ids, context=None): - result = dict.fromkeys(ids) - for obj in self.browse(cr, uid, ids, context=context): - result[obj.id] = [] - if (obj.state == 'draft' and obj.user_id): - result[obj.id] = [obj.user_id.id] - return result def case_get_note_msg_prefix(self, cr, uid, id, context=None): return 'Phonecall' diff --git a/addons/crm/crm_phonecall_view.xml b/addons/crm/crm_phonecall_view.xml index 68905d80165..6d09b2f2947 100644 --- a/addons/crm/crm_phonecall_view.xml +++ b/addons/crm/crm_phonecall_view.xml @@ -2,8 +2,44 @@ <openerp> <data> -<!-- Phonecall Categories Form View --> + <!-- Read/Unread actions --> + <record id="actions_server_crm_phonecall_unread" model="ir.actions.server"> + <field name="name">Mark unread</field> + <field name="condition">True</field> + <field name="type">ir.actions.server</field> + <field name="model_id" ref="model_crm_phonecall"/> + <field name="state">code</field> + <field name="code">self.message_check_and_set_unread(cr, uid, context.get('active_ids'), context=context)</field> + </record> + <record id="action_crm_phonecall_unread" model="ir.values"> + <field name="name">action_crm_phonecall_unread</field> + <field name="action_id" ref="actions_server_crm_phonecall_unread"/> + <field name="value" eval="'ir.actions.server,' + str(ref('actions_server_crm_phonecall_unread'))" /> + <field name="key">action</field> + <field name="model_id" ref="model_crm_phonecall" /> + <field name="model">crm.phonecall</field> + <field name="key2">client_action_multi</field> + </record> + + <record id="actions_server_crm_phonecall_read" model="ir.actions.server"> + <field name="name">Mark read</field> + <field name="condition">True</field> + <field name="type">ir.actions.server</field> + <field name="model_id" ref="model_crm_phonecall"/> + <field name="state">code</field> + <field name="code">self.message_check_and_set_read(cr, uid, context.get('active_ids'), context=context)</field> + </record> + <record id="action_crm_phonecall_read" model="ir.values"> + <field name="name">action_crm_phonecall_read</field> + <field name="action_id" ref="actions_server_crm_phonecall_read"/> + <field name="value" eval="'ir.actions.server,' + str(ref('actions_server_crm_phonecall_read'))" /> + <field name="key">action</field> + <field name="model_id" ref="model_crm_phonecall" /> + <field name="model">crm.phonecall</field> + <field name="key2">client_action_multi</field> + </record> +<!-- Phonecall Categories Form View --> <record id="crm_phonecall_categ_action" model="ir.actions.act_window"> <field name="name">Phonecall Categories</field> <field name="res_model">crm.case.categ</field> @@ -34,6 +70,7 @@ <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" @@ -123,7 +160,6 @@ <field name="type">tree</field> <field name="arch" type="xml"> <tree string="Phone Calls" fonts="bold:needaction_pending==True" editable="top"> - <field name="needaction_pending" invisible="1"/> <field name="date"/> <field name="name"/> <field name="partner_id" @@ -138,6 +174,7 @@ <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" @@ -154,7 +191,7 @@ </field> </record> -<!-- Phonecalls Calendar View --> + <!-- Phonecalls Calendar View --> <record model="ir.ui.view" id="crm_case_phone_calendar_view"> <field name="name">CRM - Phone Calls Calendar</field> @@ -169,14 +206,18 @@ </field> </record> -<!-- Phonecalls Search View --> - + <!-- Phonecalls Search View --> <record id="view_crm_case_phonecalls_filter" model="ir.ui.view"> <field name="name">CRM - Phone Calls Search</field> <field name="model">crm.phonecall</field> <field name="type">search</field> <field name="arch" type="xml"> <search string="Search Phonecalls"> + <filter icon="terp-mail-message-new" + string="Inbox" help="Unread messages" + name="needaction_pending" + domain="[('needaction_pending','=',True)]"/> + <separator orientation="vertical"/> <field name="name" string="Phonecalls"/> <separator orientation="vertical"/> <field name="date"/> diff --git a/addons/crm/crm_view.xml b/addons/crm/crm_view.xml index a733ef511f9..fc3cb3a3d2e 100644 --- a/addons/crm/crm_view.xml +++ b/addons/crm/crm_view.xml @@ -81,11 +81,16 @@ <notebook colspan="4"> <page string="Sales Team"> <group col="4"> - <field name="reply_to"/> - <field name="allow_unlink"/> - <field name="change_responsible"/> - <field name="member_ids"/> + <group> + <field name="reply_to"/> + </group> + <group> + <field name="change_responsible"/> + <field name="allow_unlink"/> + </group> </group> + <separator string="Team Members"/> + <field name="member_ids"/> </page> <page string="Stages"> <separator string="Select Stages for this Sales Team"/> diff --git a/addons/crm/res_partner_view.xml b/addons/crm/res_partner_view.xml index 19bb151b9b3..0c3750a66ac 100644 --- a/addons/crm/res_partner_view.xml +++ b/addons/crm/res_partner_view.xml @@ -99,10 +99,9 @@ context="{'default_partner_id': [active_id], 'default_duration': 4.0}"/> <button type="action" string="Schedule a Call" name="%(crm.crm_case_categ_phone_create_partner)d" - context="{'search_default_partner_id': [active_id], 'default_duration': 1.0}" /> + context="{'search_default_partner_id': [active_id], 'default_duration': 1.0, 'default_partner_id': active_id}" /> <button type="action" string="Opportunities" - context="{'search_default_partner_id': [active_id]}" - name="%(crm.action_view_crm_partner2opportunity)d" /> + name="%(crm.crm_case_category_act_oppor11)d" context="{'search_default_partner_id': [active_id]}"/> </xpath> </field> </record> diff --git a/addons/crm/wizard/crm_phonecall_to_phonecall_view.xml b/addons/crm/wizard/crm_phonecall_to_phonecall_view.xml index a9819d63886..7319c1b0437 100644 --- a/addons/crm/wizard/crm_phonecall_to_phonecall_view.xml +++ b/addons/crm/wizard/crm_phonecall_to_phonecall_view.xml @@ -17,15 +17,12 @@ <button string="Cancel" class="oe_link" special="cancel" /> </header> <group col="4"> - <separator string="Schedule/Log a Call" colspan="4"/> <field name="action"/> - <separator string="Call Details" colspan="4"/> <field name="name"/> <field name="date" string="Planned Date" attrs="{'invisible': [('action','=','log')]}"/> <field name="partner_id" readonly="True"/> <field name="user_id" /> <field name="section_id"/> - <field name="categ_id" string="Type" widget="selection" domain="[('object_id.model', '=', 'crm.phonecall')]"/> </group> </form> </field> diff --git a/addons/crm_caldav/crm_caldav_view.xml b/addons/crm_caldav/crm_caldav_view.xml index 287e301532e..5746a3af0aa 100644 --- a/addons/crm_caldav/crm_caldav_view.xml +++ b/addons/crm_caldav/crm_caldav_view.xml @@ -2,7 +2,7 @@ <openerp> <data> <record id="action_caldav_browse" model="ir.actions.act_window"> - <field name="name">Caldav Browse</field> + <field name="name">Synchronize Your Meetings</field> <field name="type">ir.actions.act_window</field> <field name="res_model">user.preference</field> <field name="view_id" ref="caldav.host_prefernce_form"/> diff --git a/addons/crm_claim/crm_claim.py b/addons/crm_claim/crm_claim.py index 808ad2e2055..5b98258240c 100644 --- a/addons/crm_claim/crm_claim.py +++ b/addons/crm_claim/crm_claim.py @@ -183,59 +183,53 @@ class crm_claim(base_stage, osv.osv): } address = self.pool.get('res.partner').browse(cr, uid, part) return {'value': {'email_from': address.email, 'partner_phone': address.phone}} - + + # ------------------------------------------------------- + # Mail gateway + # ------------------------------------------------------- + def message_new(self, cr, uid, msg, custom_values=None, context=None): - """Automatically called when new email message arrives""" - res_id = super(crm_claim,self).message_new(cr, uid, msg, custom_values=custom_values, context=context) - subject = msg.get('subject') - body = msg.get('body_text') - msg_from = msg.get('from') - priority = msg.get('priority') - vals = { - 'name': subject, - 'email_from': msg_from, + """ 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': msg.get('subject') or _("No Subject"), + 'description': msg.get('body_text'), + 'email_from': msg.get('from'), 'email_cc': msg.get('cc'), - 'description': body, - 'user_id': False, - } - if priority: - vals['priority'] = priority - vals.update(self.message_partner_by_email(cr, uid, msg.get('from', False))) - self.write(cr, uid, [res_id], vals, context=context) - return res_id + }) + 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, vals={}, default_act='pending', context=None): + 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 document according to the email. + """ if isinstance(ids, (str, int, long)): ids = [ids] - - res_id = super(crm_claim,self).message_update(cr, uid, ids, msg, context=context) + 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' } - vls = {} for line in msg['body_text'].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()) - vls[key] = res.group(2).lower() - vals.update(vls) + update_vals[key] = res.group(2).lower() - # Unfortunately the API is based on lists - # but we want to update the state based on the - # previous state, so we have to loop: - for case in self.browse(cr, uid, ids, context=context): - values = dict(vals) - if case.state in CRM_CLAIM_PENDING_STATES: - values.update(state=crm.AVAILABLE_STATES[1][0]) #re-open - res = self.write(cr, uid, [case.id], values, context=context) - return res + return super(crm_claim,self).message_update(cr, uid, ids, msg, update_vals=update_vals, context=context) # --------------------------------------------------- # OpenChatter methods and notifications diff --git a/addons/crm_fundraising/crm_fundraising.py b/addons/crm_fundraising/crm_fundraising.py index 73f72979730..20d90795ffc 100644 --- a/addons/crm_fundraising/crm_fundraising.py +++ b/addons/crm_fundraising/crm_fundraising.py @@ -127,21 +127,26 @@ class crm_fundraising(base_stage, osv.osv): self.create_send_note(cr, uid, [obj_id], context=context) return obj_id + # ------------------------------------------------------- + # Mail gateway + # ------------------------------------------------------- + def message_new(self, cr, uid, msg, custom_values=None, context=None): - """Automatically called when new email message arrives""" - res_id = super(crm_fundraising,self).message_new(cr, uid, msg, custom_values=custom_values, context=context) - vals = { - 'name': msg.get('subject'), + """ Overrides mail_thread message_new that is called by the mailgateway + through message_process. + This override also updates the document according to the email. + """ + if custom_values is None: custom_values = {} + custom_values.update({ + 'name': msg.get('subject') or _("No Subject"), + 'description': msg.get('body_text'), 'email_from': msg.get('from'), 'email_cc': msg.get('cc'), - 'description': msg.get('body_text'), - } - priority = msg.get('priority') - if priority: - vals['priority'] = priority - vals.update(self.message_partner_by_email(cr, uid, msg.get('from'))) - self.write(cr, uid, [res_id], vals, context=context) - return res_id + }) + if msg.get('priority'): + custom_values['priority'] = priority + custom_values.update(self.message_partner_by_email(cr, uid, msg.get('from'), context=context)) + return super(crm_fundraising,self).message_new(cr, uid, msg, custom_values=custom_values, context=context) # --------------------------------------------------- # OpenChatter methods and notifications diff --git a/addons/crm_fundraising/crm_fundraising_view.xml b/addons/crm_fundraising/crm_fundraising_view.xml index aefb64db4e6..212ef29a6a1 100644 --- a/addons/crm_fundraising/crm_fundraising_view.xml +++ b/addons/crm_fundraising/crm_fundraising_view.xml @@ -72,38 +72,48 @@ <field name="state" widget="statusbar" nolabel="1" statusbar_visible="draft,open,done" statusbar_colors='{"pending":"blue"}'/> </header> <sheet string="Funds Form"> - <group col="4"> - <field name="name" string="Name"/> - <field name="section_id" colspan="1" widget="selection"/> - <field name="user_id" string="Responsible"/> - <field name="date"/> - <field name="categ_id" widget="selection" domain="[('object_id.model', '=', 'crm.fundraising')]"/> - <field name="type_id" string="Payment Mode" widget="selection"/> + <label for="name" class="oe_edit_only"/> + <h1> + <field name="name"/> + </h1> + <label for="categ_id" class="oe_edit_only"/> + <h2> + <field name="categ_id" widget="selection" domain="[('object_id.model', '=', 'crm.fundraising')]" class="oe_inline"/> + </h2> + <group> + <group> + <field name="user_id" string="Responsible"/> + <field name="section_id" colspan="1" widget="selection"/> + </group> + <group> + <field name="date"/> + <field name="type_id" string="Payment Mode" widget="selection"/> + <field name="priority" string="Priority"/> + </group> </group> <notebook> <page string="Funds"> <group> - <group string="Communication"> + <group> <field name="partner_id" on_change="onchange_partner_id(partner_id, email_from)" /> <field name="email_from"/> </group> - <group string="Estimates"> + <group> <field name="planned_cost"/> <field name="planned_revenue"/> <field name="probability"/> </group> </group> <separator colspan="4" string="Notes"/> - <field name="description"/> + <field name="description" placeholder="Add a note..."/> </page> <page string="Extra Info"> <group> <group string="Misc"> <field name="active"/> <field name="id"/> - <field name="priority" string="Priority"/> </group> <group groups="base.group_no_one" string="Dates"> <field name="create_date"/> diff --git a/addons/crm_helpdesk/crm_helpdesk.py b/addons/crm_helpdesk/crm_helpdesk.py index f0d98efebb3..fe7b0581e9b 100644 --- a/addons/crm_helpdesk/crm_helpdesk.py +++ b/addons/crm_helpdesk/crm_helpdesk.py @@ -95,55 +95,51 @@ class crm_helpdesk(base_state, osv.osv): self.create_send_note(cr, uid, [obj_id], context=context) return obj_id - def message_new(self, cr, uid, msg_dict, custom_values=None, context=None): - """Automatically called when new email message arrives""" - res_id = super(crm_helpdesk,self).message_new(cr, uid, msg_dict, custom_values=custom_values, context=context) - subject = msg_dict.get('subject') or _("No Subject") - body = msg_dict.get('body_text') - msg_from = msg_dict.get('from') - vals = { - 'name': subject, - 'email_from': msg_from, - 'email_cc': msg_dict.get('cc'), - 'description': body, + # ------------------------------------------------------- + # Mail gateway + # ------------------------------------------------------- + + 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': msg.get('subject') or _("No Subject"), + 'description': msg.get('body_text'), + 'email_from': msg.get('from'), + 'email_cc': msg.get('cc'), 'user_id': False, - } - vals.update(self.message_partner_by_email(cr, uid, msg_from)) - self.write(cr, uid, [res_id], vals, context) - return res_id - - def message_update(self, cr, uid, ids, msg, vals={}, default_act='pending', context=None): + }) + 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): + """ Overrides mail_thread message_update that is called by the mailgateway + through message_process. + This method updates the document according to the email. + """ if isinstance(ids, (str, int, long)): ids = [ids] - - super(crm_helpdesk,self).message_update(cr, uid, ids, msg, context=context) + 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' } - vls = {} for line in msg['body_text'].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()) - vls[key] = res.group(2).lower() - vals.update(vls) - - # Unfortunately the API is based on lists - # but we want to update the state based on the - # previous state, so we have to loop: - for case in self.browse(cr, uid, ids, context=context): - values = dict(vals) - if case.state in CRM_HELPDESK_STATES: - values.update(state=crm.AVAILABLE_STATES[1][0]) #re-open - res = self.write(cr, uid, [case.id], values, context=context) - return res + update_vals[key] = res.group(2).lower() + + return super(crm_helpdesk,self).message_update(cr, uid, ids, msg, update_vals=update_vals, context=context) # ****************************** # OpenChatter diff --git a/addons/event/event.py b/addons/event/event.py index a348e005d06..26fef61d2b5 100644 --- a/addons/event/event.py +++ b/addons/event/event.py @@ -271,13 +271,6 @@ class event_event(osv.osv): # OpenChatter methods and notifications # ---------------------------------------- - def get_needaction_user_ids(self, cr, uid, ids, context=None): - result = dict.fromkeys(ids, []) - for obj in self.browse(cr, uid, ids, context=context): - if obj.state == 'draft' and obj.user_id: - result[obj.id] = [obj.user_id.id] - return result - 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) @@ -451,13 +444,6 @@ class event_registration(osv.osv): # OpenChatter methods and notifications # ---------------------------------------- - def get_needaction_user_ids(self, cr, uid, ids, context=None): - result = dict.fromkeys(ids, []) - for obj in self.browse(cr, uid, ids, context=context): - if obj.state == 'draft' and obj.user_id: - result[obj.id] = [obj.user_id.id] - return result - 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) diff --git a/addons/event/event_view.xml b/addons/event/event_view.xml index 0e9647d496d..fcf8231f5f7 100644 --- a/addons/event/event_view.xml +++ b/addons/event/event_view.xml @@ -47,6 +47,43 @@ <!-- Events Organisation/CONFIGURATION/EVENTS --> + <!-- Event Read/Unread actions --> + <record id="actions_server_event_event_unread" model="ir.actions.server"> + <field name="name">Mark unread</field> + <field name="condition">True</field> + <field name="type">ir.actions.server</field> + <field name="model_id" ref="model_event_event"/> + <field name="state">code</field> + <field name="code">self.message_check_and_set_unread(cr, uid, context.get('active_ids'), context=context)</field> + </record> + <record id="action_event_event_unread" model="ir.values"> + <field name="name">action_event_event_unread</field> + <field name="action_id" ref="actions_server_event_event_unread"/> + <field name="value" eval="'ir.actions.server,' + str(ref('actions_server_event_event_unread'))" /> + <field name="key">action</field> + <field name="model_id" ref="model_event_event" /> + <field name="model">event.event</field> + <field name="key2">client_action_multi</field> + </record> + + <record id="actions_server_event_event_read" model="ir.actions.server"> + <field name="name">Mark read</field> + <field name="condition">True</field> + <field name="type">ir.actions.server</field> + <field name="model_id" ref="model_event_event"/> + <field name="state">code</field> + <field name="code">self.message_check_and_set_read(cr, uid, context.get('active_ids'), context=context)</field> + </record> + <record id="action_event_event_read" model="ir.values"> + <field name="name">action_event_event_read</field> + <field name="action_id" ref="actions_server_event_event_read"/> + <field name="value" eval="'ir.actions.server,' + str(ref('actions_server_event_event_read'))" /> + <field name="key">action</field> + <field name="model_id" ref="model_event_event" /> + <field name="model">event.event</field> + <field name="key2">client_action_multi</field> + </record> + <record model="ir.ui.view" id="view_event_form"> <field name="name">Events</field> <field name="model">event.event</field> @@ -144,7 +181,7 @@ <field name="model">event.event</field> <field name="type">tree</field> <field name="arch" type="xml"> - <tree string="Events" colors="red:(register_min and register_min>register_current) or (register_max and register_max<register_current);grey:state=='cancel'"> + <tree string="Events" fonts="bold:needaction_pending==True" colors="red:(register_min and register_min>register_current) or (register_max and register_max<register_current);grey:state=='cancel'"> <field name="name" string="Name"/> <field name="type"/> <field name="date_begin"/> @@ -155,6 +192,7 @@ <field name="main_speaker_id" groups="base.extended"/> <field name="user_id"/> <field name="state"/> + <field name="needaction_pending" invisible="1"/> </tree> </field> </record> @@ -264,6 +302,11 @@ <field name="type">search</field> <field name="arch" type="xml"> <search string="Events"> + <filter icon="terp-mail-message-new" + string="Inbox" help="Unread messages" + name="needaction_pending" + domain="[('needaction_pending','=',True)]"/> + <separator orientation="vertical"/> <group> <field name="name" string="Events"/> <separator orientation="vertical"/> @@ -330,12 +373,49 @@ <!-- EVENTS/REGISTRATIONS/EVENTS --> + <!-- Registration Read/Unread actions --> + <record id="actions_server_event_registration_unread" model="ir.actions.server"> + <field name="name">Mark unread</field> + <field name="condition">True</field> + <field name="type">ir.actions.server</field> + <field name="model_id" ref="model_event_registration"/> + <field name="state">code</field> + <field name="code">self.message_check_and_set_unread(cr, uid, context.get('active_ids'), context=context)</field> + </record> + <record id="action_event_registration_unread" model="ir.values"> + <field name="name">action_event_registration_unread</field> + <field name="action_id" ref="actions_server_event_registration_unread"/> + <field name="value" eval="'ir.actions.server,' + str(ref('actions_server_event_registration_unread'))" /> + <field name="key">action</field> + <field name="model_id" ref="model_event_registration" /> + <field name="model">event.registration</field> + <field name="key2">client_action_multi</field> + </record> + + <record id="actions_server_event_registration_read" model="ir.actions.server"> + <field name="name">Mark read</field> + <field name="condition">True</field> + <field name="type">ir.actions.server</field> + <field name="model_id" ref="model_event_registration"/> + <field name="state">code</field> + <field name="code">self.message_check_and_set_read(cr, uid, context.get('active_ids'), context=context)</field> + </record> + <record id="action_event_registration_read" model="ir.values"> + <field name="name">action_event_registration_read</field> + <field name="action_id" ref="actions_server_event_registration_read"/> + <field name="value" eval="'ir.actions.server,' + str(ref('actions_server_event_registration_read'))" /> + <field name="key">action</field> + <field name="model_id" ref="model_event_registration" /> + <field name="model">event.registration</field> + <field name="key2">client_action_multi</field> + </record> + <record model="ir.ui.view" id="view_event_registration_tree"> <field name="name">event.registration.tree</field> <field name="model">event.registration</field> <field name="type">tree</field> <field name="arch" type="xml"> - <tree string="Registration" > + <tree string="Registration" fonts="bold:needaction_pending==True"> <field name="create_date"/> <field name="partner_id"/> <field name="name"/> @@ -345,6 +425,7 @@ <field name="user_id"/> <field name="origin"/> <field name="state"/> + <field name="needaction_pending" invisible="1"/> </tree> </field> </record> @@ -449,6 +530,11 @@ <field name="arch" type="xml"> <search string="Event Registration"> <group> + <filter icon="terp-mail-message-new" + string="Inbox" help="Unread messages" + name="needaction_pending" + domain="[('needaction_pending','=',True)]"/> + <separator orientation="vertical"/> <field name="name" string="Participant" filter_domain="['|','|','|',('name','ilike',self),('partner_id','ilike',self),('email','ilike',self),('origin','ilike',self)]"/> <separator orientation="vertical"/> diff --git a/addons/fetchmail/fetchmail.py b/addons/fetchmail/fetchmail.py index cddcf642dc4..55923c3043a 100644 --- a/addons/fetchmail/fetchmail.py +++ b/addons/fetchmail/fetchmail.py @@ -151,7 +151,7 @@ openerp_mailgate.py -u %(uid)d -p PASSWORD -o %(model)s -d %(dbname)s --host=HOS connection = server.connect() server.write({'state':'done'}) except Exception, e: - logger.exception("Failed to connect to %s server %s", server.type, server.name) + _logger.exception("Failed to connect to %s server %s", server.type, server.name) raise osv.except_osv(_("Connection test failed!"), _("Here is what we got instead:\n %s") % tools.ustr(e)) finally: try: @@ -177,7 +177,7 @@ openerp_mailgate.py -u %(uid)d -p PASSWORD -o %(model)s -d %(dbname)s --host=HOS mail_thread = self.pool.get('mail.thread') action_pool = self.pool.get('ir.actions.server') for server in self.browse(cr, uid, ids, context=context): - logger.info('start checking for new emails on %s server %s', server.type, server.name) + _logger.info('start checking for new emails on %s server %s', server.type, server.name) context.update({'fetchmail_server_id': server.id, 'server_type': server.type}) count = 0 imap_server = False @@ -198,9 +198,9 @@ openerp_mailgate.py -u %(uid)d -p PASSWORD -o %(model)s -d %(dbname)s --host=HOS imap_server.store(num, '+FLAGS', '\\Seen') cr.commit() count += 1 - logger.info("fetched/processed %s email(s) on %s server %s", count, server.type, server.name) + _logger.info("fetched/processed %s email(s) on %s server %s", count, server.type, server.name) except Exception, e: - logger.exception("Failed to fetch mail from %s server %s", server.type, server.name) + _logger.exception("Failed to fetch mail from %s server %s", server.type, server.name) finally: if imap_server: imap_server.close() @@ -222,9 +222,9 @@ openerp_mailgate.py -u %(uid)d -p PASSWORD -o %(model)s -d %(dbname)s --host=HOS action_pool.run(cr, uid, [server.action_id.id], {'active_id': res_id, 'active_ids':[res_id]}) pop_server.dele(num) cr.commit() - logger.info("fetched/processed %s email(s) on %s server %s", numMsgs, server.type, server.name) + _logger.info("fetched/processed %s email(s) on %s server %s", numMsgs, server.type, server.name) except Exception, e: - logger.exception("Failed to fetch mail from %s server %s", server.type, server.name) + _logger.exception("Failed to fetch mail from %s server %s", server.type, server.name) finally: if pop_server: pop_server.quit() diff --git a/addons/hr_holidays/hr_holidays.py b/addons/hr_holidays/hr_holidays.py index f5e8ba69e5d..a1ab0ae04f3 100644 --- a/addons/hr_holidays/hr_holidays.py +++ b/addons/hr_holidays/hr_holidays.py @@ -359,7 +359,7 @@ class hr_holidays(osv.osv): # ----------------------------- def get_needaction_user_ids(self, cr, uid, ids, context=None): - result = dict.fromkeys(ids, []) + 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.employee_id.parent_id: result[obj.id] = [obj.employee_id.parent_id.user_id.id] @@ -371,14 +371,16 @@ class hr_holidays(osv.osv): 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_subscribers(self, cr, uid, ids, context=None): - sub_ids = self.message_get_subscribers_ids(cr, uid, ids, context=context); - # add the employee and its manager if specified to the subscribed users + """ Override to add employee and its manager. """ + user_ids = super(hr_holidays, self).message_get_subscribers(cr, uid, ids, context=context) for obj in self.browse(cr, uid, ids, context=context): - if obj.employee_id.parent_id: - sub_ids.append(obj.employee_id.parent_id.user_id.id) - return self.pool.get('res.users').read(cr, uid, sub_ids, context=context) + if obj.user_id and not obj.user_id.id in user_ids: + user_ids.append(obj.user_id.id) + if obj.employee_id.parent_id and not obj.employee_id.parent_id.user_id.id in user_ids: + user_ids.append(obj.employee_id.parent_id.user_id.id) + return user_ids def create_notificate(self, cr, uid, ids, context=None): for obj in self.browse(cr, uid, ids, context=context): diff --git a/addons/hr_holidays/hr_holidays_view.xml b/addons/hr_holidays/hr_holidays_view.xml index 31c50e86bdf..bda18f1bc6c 100644 --- a/addons/hr_holidays/hr_holidays_view.xml +++ b/addons/hr_holidays/hr_holidays_view.xml @@ -1,6 +1,43 @@ <openerp> <data> + <!-- Hr holidays Read/Unread actions --> + <record id="actions_server_holidays_unread" model="ir.actions.server"> + <field name="name">Mark unread</field> + <field name="condition">True</field> + <field name="type">ir.actions.server</field> + <field name="model_id" ref="model_hr_holidays"/> + <field name="state">code</field> + <field name="code">self.message_check_and_set_unread(cr, uid, context.get('active_ids'), context=context)</field> + </record> + <record id="action_holidays_unread" model="ir.values"> + <field name="name">action_holidays_unread</field> + <field name="action_id" ref="actions_server_holidays_unread"/> + <field name="value" eval="'ir.actions.server,' + str(ref('actions_server_holidays_unread'))" /> + <field name="key">action</field> + <field name="model_id" ref="model_hr_holidays" /> + <field name="model">hr.holidays</field> + <field name="key2">client_action_multi</field> + </record> + + <record id="actions_server_holidays_read" model="ir.actions.server"> + <field name="name">Mark read</field> + <field name="condition">True</field> + <field name="type">ir.actions.server</field> + <field name="model_id" ref="model_hr_holidays"/> + <field name="state">code</field> + <field name="code">self.message_check_and_set_read(cr, uid, context.get('active_ids'), context=context)</field> + </record> + <record id="action_holidays_read" model="ir.values"> + <field name="name">action_holidays_read</field> + <field name="action_id" ref="actions_server_holidays_read"/> + <field name="value" eval="'ir.actions.server,' + str(ref('actions_server_holidays_read'))" /> + <field name="key">action</field> + <field name="model_id" ref="model_hr_holidays" /> + <field name="model">hr.holidays</field> + <field name="key2">client_action_multi</field> + </record> + <record id="view_hr_holidays_filter" model="ir.ui.view"> <field name="name">hr.holidays.filter</field> <field name="model">hr.holidays</field> @@ -10,6 +47,11 @@ <group> <field name="date_from"/> <separator orientation="vertical"/> + <filter icon="terp-mail-message-new" + string="Inbox" help="Unread messages" + name="needaction_pending" + domain="[('needaction_pending','=',True)]"/> + <separator orientation="vertical"/> <filter string="My Leaves" icon="terp-personal" name="my_leaves" domain="[('employee_id.user_id','=', uid)]" help="My Leaves"/> <filter string="My Department Leaves" icon="terp-personal+" help="My Department Leaves" domain="[('department_id.manager_id','=',uid)]"/> <filter icon="terp-check" domain="[('state','=','draft')]" string="To Confirm"/> diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py index c893755c7c0..7b0090d60ea 100644 --- a/addons/hr_recruitment/hr_recruitment.py +++ b/addons/hr_recruitment/hr_recruitment.py @@ -357,54 +357,53 @@ class hr_applicant(base_stage, osv.Model): return value def message_new(self, cr, uid, msg, custom_values=None, context=None): - """Automatically called when new email message arrives""" - res_id = super(hr_applicant,self).message_new(cr, uid, msg, custom_values=custom_values, context=context) - subject = msg.get('subject') or _("No Subject") - body = msg.get('body_text') - msg_from = msg.get('from') - priority = msg.get('priority') - vals = { - 'name': subject, - 'email_from': msg_from, + """ 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': msg.get('subject') or _("No Subject"), + 'description': msg.get('body_text'), + 'email_from': msg.get('from'), 'email_cc': msg.get('cc'), - 'description': body, 'user_id': False, - } - if priority: - vals['priority'] = priority - vals.update(self.message_partner_by_email(cr, uid, msg.get('from', False))) - self.write(cr, uid, [res_id], vals, context) - return res_id - - def message_update(self, cr, uid, ids, msg, vals=None, default_act='pending', context=None): + }) + 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): + """ Override mail_thread message_update that is called by the mailgateway + through message_process. + This method updates the document according to the email. + """ if isinstance(ids, (str, int, long)): ids = [ids] - if vals is None: - vals = {} - msg_from = msg['from'] - vals.update({ - 'description': msg['body_text'] + if update_vals is None: vals = {} + + update_vals.update({ + 'description': msg.get('body'), + 'email_from': msg.get('from'), + 'email_cc': msg.get('cc'), }) - if msg.get('priority', False): - vals['priority'] = msg.get('priority') + if msg.get('priority'): + update_vals['priority'] = msg.get('priority') maps = { - 'cost':'planned_cost', + 'cost': 'planned_cost', 'revenue': 'planned_revenue', - 'probability':'probability' + 'probability': 'probability', } - vls = { } - for line in msg['body_text'].split('\n'): + for line in msg.get('body_text', '').split('\n'): line = line.strip() res = tools.misc.command_re.match(line) if res and maps.get(res.group(1).lower(), False): key = maps.get(res.group(1).lower()) - vls[key] = res.group(2).lower() + update_vals[key] = res.group(2).lower() - vals.update(vls) - res = self.write(cr, uid, ids, vals, context=context) - self.message_append_dict(cr, uid, ids, msg, context=context) - return res + return super(hr_applicant, self).message_update(cr, uids, ids, update_vals=update_vals, context=context) def create(self, cr, uid, vals, context=None): obj_id = super(hr_applicant, self).create(cr, uid, vals, context=context) @@ -493,20 +492,14 @@ class hr_applicant(base_stage, osv.Model): # ------------------------------------------------------- # OpenChatter methods and notifications # ------------------------------------------------------- - - def message_get_subscribers(self, cr, uid, ids, context=None): - sub_ids = self.message_get_subscribers_ids(cr, uid, ids, context=context); - for obj in self.browse(cr, uid, ids, context=context): - if obj.user_id: - sub_ids.append(obj.user_id.id) - return self.pool.get('res.users').read(cr, uid, sub_ids, context=context) - def get_needaction_user_ids(self, cr, uid, ids, context=None): - result = dict.fromkeys(ids, []) + def message_get_subscribers(self, cr, uid, ids, context=None): + """ Override to add responsible user. """ + user_ids = super(hr_applicant, self).message_get_subscribers(cr, uid, ids, context=context) for obj in self.browse(cr, uid, ids, context=context): - if obj.state == 'draft' and obj.user_id: - result[obj.id] = [obj.user_id.id] - return result + if obj.user_id and not obj.user_id.id in user_ids: + user_ids.append(obj.user_id.id) + return user_ids def stage_set_send_note(self, cr, uid, ids, stage_id, context=None): """ Override of the (void) default notification method. """ diff --git a/addons/hr_recruitment/hr_recruitment_view.xml b/addons/hr_recruitment/hr_recruitment_view.xml index 86cb259dad4..00b300cb6f6 100644 --- a/addons/hr_recruitment/hr_recruitment_view.xml +++ b/addons/hr_recruitment/hr_recruitment_view.xml @@ -45,6 +45,43 @@ # Jobs # ------------------------------------------------------ + <!-- Applicant Read/Unread actions --> + <record id="actions_server_applicant_unread" model="ir.actions.server"> + <field name="name">Mark unread</field> + <field name="condition">True</field> + <field name="type">ir.actions.server</field> + <field name="model_id" ref="model_hr_applicant"/> + <field name="state">code</field> + <field name="code">self.message_check_and_set_unread(cr, uid, context.get('active_ids'), context=context)</field> + </record> + <record id="action_applicant_unread" model="ir.values"> + <field name="name">action_project_unread</field> + <field name="action_id" ref="actions_server_applicant_unread"/> + <field name="value" eval="'ir.actions.server,' + str(ref('actions_server_applicant_unread'))" /> + <field name="key">action</field> + <field name="model_id" ref="model_hr_applicant" /> + <field name="model">hr.applicant</field> + <field name="key2">client_action_multi</field> + </record> + + <record id="actions_server_applicant_read" model="ir.actions.server"> + <field name="name">Mark read</field> + <field name="condition">True</field> + <field name="type">ir.actions.server</field> + <field name="model_id" ref="model_hr_applicant"/> + <field name="state">code</field> + <field name="code">self.message_check_and_set_read(cr, uid, context.get('active_ids'), context=context)</field> + </record> + <record id="action_applicant_read" model="ir.values"> + <field name="name">action_project_read</field> + <field name="action_id" ref="actions_server_applicant_read"/> + <field name="value" eval="'ir.actions.server,' + str(ref('actions_server_applicant_read'))" /> + <field name="key">action</field> + <field name="model_id" ref="model_hr_applicant" /> + <field name="model">hr.applicant</field> + <field name="key2">client_action_multi</field> + </record> + <record model="ir.ui.view" id="crm_case_tree_view_job"> <field name="name">Applicants</field> <field name="model">hr.applicant</field> @@ -192,6 +229,12 @@ <field name="type">search</field> <field name="arch" type="xml"> <search string="Search Jobs"> + <filter icon="terp-mail-message-new" + string="Inbox" help="Unread messages" + name="needaction_pending" + domain="[('needaction_pending','=',True)]"/> + <separator orientation="vertical"/> + <filter icon="terp-document-new" string="New" name="filter_new"/> <field name="partner_name" filter_domain="['|','|',('name','ilike',self),('partner_name','ilike',self),('email_from','ilike',self)]" string="Subject / Applicant"/> <separator orientation="vertical"/> <filter icon="terp-document-new" string="New" diff --git a/addons/import_sugarcrm/import_sugarcrm.py b/addons/import_sugarcrm/import_sugarcrm.py index fa89ad1949e..42ba4382b27 100644 --- a/addons/import_sugarcrm/import_sugarcrm.py +++ b/addons/import_sugarcrm/import_sugarcrm.py @@ -949,7 +949,7 @@ class import_sugarcrm(osv.osv): 'user' : fields.boolean('User', help="Check this box to import sugarCRM Users into OpenERP users, warning if a user with the same login exist in OpenERP, user information will be erase by sugarCRM user information", readonly=True), 'opportunity': fields.boolean('Leads & Opp', help="Check this box to import sugarCRM Leads and Opportunities into OpenERP Leads and Opportunities"), 'contact': fields.boolean('Contacts', help="Check this box to import sugarCRM Contacts into OpenERP addresses"), - 'account': fields.boolean('Accounts', help="Check this box to import sugarCRM Accounts into OpenERP partners"), + 'account': fields.boolean('Partner/Account', help="Check this box to import sugarCRM Accounts into OpenERP partners"), 'employee': fields.boolean('Employee', help="Check this box to import sugarCRM Employees into OpenERP employees"), 'meeting': fields.boolean('Meetings', help="Check this box to import sugarCRM Meetings and Tasks into OpenERP meetings"), 'call': fields.boolean('Calls', help="Check this box to import sugarCRM Calls into OpenERP calls"), diff --git a/addons/import_sugarcrm/import_sugarcrm_view.xml b/addons/import_sugarcrm/import_sugarcrm_view.xml index 8dfc984c6c0..7188108ba6c 100644 --- a/addons/import_sugarcrm/import_sugarcrm_view.xml +++ b/addons/import_sugarcrm/import_sugarcrm_view.xml @@ -9,76 +9,58 @@ <field name="type">form</field> <field name="arch" type="xml"> <form string="Import Data From SugarCRM" version="7.0"> - <header> - <button name="import_from_scheduler_all" string="_Schedule Recurrent Imports" type="object" class="oe_highlight" /> - or - <button name="import_all" string="_Import" type="object" class="oe_highlight" /> - or - <button string="Cancel" class="oe_link" special="cancel" /> - </header> - <group col="8" > - <group colspan="2" col="2" width="200"> - <label colspan="2" string="Import your data from SugarCRM :"/> - <label colspan="2" string="" /> - <label colspan="2" string="Use the SugarSoap API URL (read tooltip) and a full access SugarCRM login."/> - <label colspan="2" string="" /> - <label colspan="2" string="Choose data you want to import. Click 'Import' to get data manually or 'Schedule Reccurent Imports' to get recurrently and automatically data."/> - <label colspan="2" string="" /> - <label colspan="2" string="If you make recurrent or ponctual import, data already in OpenERP will be updated by SugarCRM data."/> - <label colspan="2" string="" /> - <label colspan="2" string="Do not forget the email address to be notified of the success of the import."/> - <label colspan="2" /> - <label colspan="2" string="Online documentation:"/> - <label colspan="2" string="(Coming Soon)"/> - <label colspan="2" string=""/> - </group> - <separator string="" orientation="vertical" colspan="1" rowspan="24" /> - <group colspan="5"> - <separator string="Login Information" colspan="4"/> - <field name="url" colspan="4" widget="url"/> - <field name="username" colspan="4"/> - <field name="password" password="True" colspan="4"/> + <header> + <button name="import_all" string="_Import" + type="object" class="oe_highlight"/> + <button name="import_from_scheduler_all" string="_Schedule Recurrent Imports" + type="object" class="oe_highlight" /> + <button special="cancel" string="_Cancel" class="oe_link"/> + </header> + <sheet> + <group col="8" > + <group colspan="5"> + <separator string="Login Information" colspan="4"/> + <field name="url" colspan="2" widget="url"/> + <field name="username" colspan="2"/> + <field name="password" password="True" colspan="2"/> - <group colspan="4"> - <group colspan="1" col="2"> - <separator string="Address Book" colspan="2"/> - <field name="user" /> - <field name= "account" /> - <field name= "contact" /> - </group> - <group colspan="1" col="2"> - <separator string="CRM" colspan="2"/> - <field name="opportunity" /> - <field name= "call" /> - <field name= "meeting" /> - <field name= "claim" /> - </group> - <group colspan="1" col="2"> - <separator string="Project" colspan="2"/> - <field name= "project" /> - <field name= "project_task" /> - <field name= "bug"/> - </group> - <group colspan="1" col="2"> - <separator string="HR" colspan="2"/> - <field name="employee" /> - <separator string="Document" colspan="2"/> - <field name="email_history"/> - <field name= "document" /> - </group> - </group> + <group colspan="4"> + <separator string="Data to Import" colspan="4"/> + <group colspan="1" col="2"> + <field name="user" /> + <field name= "account" /> + <field name= "contact" /> + </group> + <group colspan="1" col="2"> + <field name="opportunity" /> + <field name= "call" /> + <field name= "meeting" /> + <field name= "claim" /> + </group> + <group colspan="1" col="2"> + <field name= "project" /> + <field name= "project_task" /> + <field name= "bug"/> + </group> + <group colspan="1" col="2"> + <field name="employee" /> + <field name="email_history"/> + <field name= "document" /> + </group> + </group> - <group colspan="4"> - <separator string="Email Notification When Import is Finished" colspan="4"/> - <field name="email_from" widget="email" string="Email Address to Notify" colspan="4"/> - </group> + <group colspan="4"> + <separator string="Email Notification When Import is Finished" colspan="2"/> + <field name="email_from" widget="email" string="Email Address to Notify" colspan="2"/> + </group> - <group colspan="4" groups="base.group_no_one"> - <separator string="Multi Instance Management" colspan="4"/> - <field name="instance_name" colspan="4"/> + <group colspan="4" groups="base.group_no_one"> + <separator string="Multi Instance Management" colspan="4"/> + <field name="instance_name" colspan="2"/> + </group> + </group> </group> - </group> - </group> + </sheet> </form> </field> </record> diff --git a/addons/mail/__init__.py b/addons/mail/__init__.py index d9835f86fc5..805c27386f3 100644 --- a/addons/mail/__init__.py +++ b/addons/mail/__init__.py @@ -23,6 +23,7 @@ import mail_message import mail_thread import mail_group import mail_subscription +import ir_needaction import res_users import res_partner import report diff --git a/addons/mail/__openerp__.py b/addons/mail/__openerp__.py index 95ce7d5619d..f501cc58d2c 100644 --- a/addons/mail/__openerp__.py +++ b/addons/mail/__openerp__.py @@ -86,6 +86,7 @@ The main features are: 'static/src/css/mail_group.css', ], 'js': [ + 'static/lib/jquery.expander/jquery.expander.js', 'static/src/js/mail.js', ], 'qweb': [ diff --git a/addons/mail/doc/index.rst.inc b/addons/mail/doc/index.rst.inc index 822d855d5b4..c4b87cc18f9 100644 --- a/addons/mail/doc/index.rst.inc +++ b/addons/mail/doc/index.rst.inc @@ -10,3 +10,4 @@ Mail Module documentation topics mail_openchatter_howto mail_needaction_howto mail_partner + mail_state diff --git a/addons/mail/doc/mail_state.rst b/addons/mail/doc/mail_state.rst new file mode 100644 index 00000000000..527a31ba835 --- /dev/null +++ b/addons/mail/doc/mail_state.rst @@ -0,0 +1,28 @@ +.. _mail_state: + +message_state +============= + +``message_state`` 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 ++++++++++++++++++++++++++++++++++++++++ + +The ``mail`` module introduces a default behavior for the need_action +mechanism [REF]. + +:: + + def get_needaction_user_ids(self, cr, uid, ids, context=None): + """ Returns the user_ids that have to perform an action + :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 diff --git a/addons/mail/ir_needaction.py b/addons/mail/ir_needaction.py new file mode 100644 index 00000000000..8193ac4f385 --- /dev/null +++ b/addons/mail/ir_needaction.py @@ -0,0 +1,46 @@ +# -*- 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: diff --git a/addons/mail/mail_group.py b/addons/mail/mail_group.py index 0a8677d7862..f691d75e0bc 100644 --- a/addons/mail/mail_group.py +++ b/addons/mail/mail_group.py @@ -87,7 +87,7 @@ class mail_group(osv.osv): result = dict.fromkeys(ids) for id in ids: result[id] = {} - result[id]['member_ids'] = self.message_get_subscribers_ids(cr, uid, [id], context=context) + result[id]['member_ids'] = self.message_get_subscribers(cr, uid, [id], context=context) result[id]['member_count'] = len(result[id]['member_ids']) result[id]['is_subscriber'] = uid in result[id]['member_ids'] return result diff --git a/addons/mail/mail_group_view.xml b/addons/mail/mail_group_view.xml index 921c3b528c4..4b9cfd09c3d 100644 --- a/addons/mail/mail_group_view.xml +++ b/addons/mail/mail_group_view.xml @@ -51,28 +51,30 @@ <field name="priority" eval="10"/> <field name="arch" type="xml"> <form string="Group Form" version="7.0"> - <div style="padding: 8px"> - <div class="oe_right"> - <field name="photo" widget='image' on_change="onchange_photo(photo)"/> - </div> - <div class="oe_title"> - <div class="oe_edit_only"> - <label for="name"/> - </div> - <h1><field name="name"/></h1> - <div class="oe_edit_only"> - <label for="responsible_id"/> - </div> - <field name="responsible_id" class="oe_inline"/> - <div class="oe_edit_only"> - <label for="public"/> - <field name="public" class="oe_inline"/> - </div> - <field name="description" colspan="4" nolabel="1"/> - </div> - </div> - <footer> - <field name="message_ids" colspan="4" widget="ThreadView" options='{"thread_level": 1}'/> + <sheet class="openerp_mail_group_sheet"> + <group colspan="4" col="8"> + <group colspan="1" col="2"> + <field name="photo" widget='image' nolabel="1" on_change="onchange_photo(photo)"/> + </group> + <group colspan="7" col="6"> + <div class="oe_edit_only"> + <label for="name"/> + </div> + <h2><field name="name" colspan="4"/></h2> + <newline/> + <group colspan="4" col="4"> + <field name="description" colspan="4" nolabel="1"/> + </group> + <group colspan="2" col="2" class="oe_edit_only"> + <field name="responsible_id" colspan="2"/> + <field name="public" colspan="2"/> + </group> + </group> + </group> + </sheet> + <footer class="openerp_mail_group_footer"> + <field name="message_ids" widget="ThreadView" + options='{"thread_level": 1}'/> </footer> </form> </field> diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py index 1c1e980e3ed..301657ba4c2 100644 --- a/addons/mail/mail_message.py +++ b/addons/mail/mail_message.py @@ -96,11 +96,24 @@ class mail_message_common(osv.osv_memory): continue result[message.id] = self.pool.get(message.model).name_get(cr, uid, [message.res_id], context=context)[0][1] return result - + + def name_get(self, cr, uid, ids, context=None): + 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)) + return res + _name = 'mail.message.common' _rec_name = 'subject' _columns = { - 'subject': fields.char('Subject', size=512, required=True), + 'subject': fields.char('Subject', size=512), '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', string='Message Record Name', diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index e0f2bfaf455..5668709af17 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -34,7 +34,7 @@ import xmlrpclib _logger = logging.getLogger(__name__) -class mail_thread(osv.osv): +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 @@ -62,10 +62,15 @@ class mail_thread(osv.osv): _name = 'mail.thread' _description = 'Email Thread' - def _get_message_ids(self, cr, uid, ids, name, arg, context=None): + def _get_message_ids(self, cr, uid, ids, name, args, context=None): res = {} for id in ids: - res[id] = self.message_load_ids(cr, uid, [id], context=context) + message_ids = self.message_load_ids(cr, uid, [id], context=context) + subscriber_ids = self.message_get_subscribers(cr, uid, [id], context=context) + res[id] = { + 'message_ids': message_ids, + 'message_summary': "<span>Msg: %d</span> . <span>Fol: %d</span>" % (len(message_ids), len(subscriber_ids)), + } return res def _search_message_ids(self, cr, uid, obj, name, args, context=None): @@ -73,10 +78,23 @@ class mail_thread(osv.osv): msg_ids = msg_obj.search(cr, uid, ['&', ('res_id', 'in', args[0][2]), ('model', '=', self._name)], context=context) return [('id', 'in', msg_ids)] - # OpenChatter: message_ids is a dummy field that should not be used _columns = { - 'message_ids': fields.function(_get_message_ids, method=True, fnct_search=_search_message_ids, - type='one2many', obj='mail.message', string='Temp messages', _fields_id = 'res_id'), + 'message_ids': fields.function(_get_message_ids, method=True, + fnct_search=_search_message_ids, + type='one2many', obj='mail.message', _fields_id = 'res_id', + string='Temp messages', multi="_get_message_ids", + help="Functional field holding messages related to the current document."), + 'message_state': fields.boolean('Read', + help="When checked, new messages require your attention."), + 'message_summary': fields.function(_get_message_ids, method=True, + type='text', string='Summary', multi="_get_message_ids", + 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, } #------------------------------------------------------ @@ -84,10 +102,11 @@ class mail_thread(osv.osv): #------------------------------------------------------ def create(self, cr, uid, vals, context=None): - """Automatically subscribe the creator""" - thread_id = super(mail_thread, self).create(cr, uid, vals, context=context); - self.message_subscribe(cr, uid, [thread_id], [uid], context=context) - return thread_id; + """Automatically subscribe the creator """ + thread_id = super(mail_thread, self).create(cr, uid, vals, context=context) + if thread_id: + self.message_subscribe(cr, uid, [thread_id], [uid], context=context) + return thread_id def write(self, cr, uid, ids, vals, context=None): """Automatically subscribe the writer""" @@ -106,8 +125,6 @@ class mail_thread(osv.osv): a foreign key with a 'cascade' ondelete attribute. Notifications will be deleted with messages """ - if context is None: - context = {} subscr_obj = self.pool.get('mail.subscription') msg_obj = self.pool.get('mail.message') # delete subscriptions @@ -120,17 +137,18 @@ class mail_thread(osv.osv): return super(mail_thread, self).unlink(cr, uid, ids, context=context) #------------------------------------------------------ - # Generic message api + # mail.message wrappers and tools #------------------------------------------------------ def message_create(self, cr, uid, thread_id, vals, context=None): - """OpenSocial: wrapper of mail.message create method + """ OpenChatter: wrapper of mail.message create method - creates the mail.message - automatically subscribe the message writer - push the message to subscribed users """ if context is None: context = {} + message_obj = self.pool.get('mail.message') subscription_obj = self.pool.get('mail.subscription') notification_obj = self.pool.get('mail.notification') @@ -167,7 +185,10 @@ class mail_thread(osv.osv): # create message msg_id = message_obj.create(cr, uid, vals, 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 True @@ -186,7 +207,7 @@ class mail_thread(osv.osv): body = new_msg_vals.get('body_html', '') if new_msg_vals.get('subtype', 'plain') == 'html' else new_msg_vals.get('body_text', '') for thread_id in thread_ids: # add subscribers - notif_user_ids += [user['id'] for user in self.message_get_subscribers(cr, uid, [thread_id], context=context)] + notif_user_ids += self.message_get_subscribers(cr, uid, [thread_id], context=context) # add users requested via parsing message (@login) notif_user_ids += self.message_parse_users(cr, uid, [thread_id], body, context=context) # add users requested to perform an action (need_action mechanism) @@ -214,6 +235,10 @@ class mail_thread(osv.osv): user_ids = self.pool.get('res.users').search(cr, uid, [('login', 'in', login_lst)], context=context) return user_ids + #------------------------------------------------------ + # Generic message api + #------------------------------------------------------ + def message_capable_models(self, cr, uid, context=None): ret_dict = {} for model_name in self.pool.obj_list(): @@ -376,7 +401,6 @@ class mail_thread(osv.osv): original = msg_dict.get('original'), context = context) - # Message loading def _message_add_ancestor_ids(self, cr, uid, ids, child_ids, root_ids, context=None): """ Given message child_ids Find their ancestors until root ids""" @@ -420,6 +444,9 @@ class mail_thread(osv.osv): msg_ids = self.message_load_ids(cr, uid, ids, limit, offset, domain, ascent, root_ids, context=context) msgs = self.pool.get('mail.message').read(cr, uid, msg_ids, [], context=context) + # Set as read + self.message_check_and_set_read(cr, uid, ids, context=context) + """ Retrieve all attachments names """ map_id_to_name = {} @@ -474,7 +501,7 @@ class mail_thread(osv.osv): return msgs #------------------------------------------------------ - # Email specific + # Mail gateway #------------------------------------------------------ # message_process will call either message_new or message_update. @@ -549,7 +576,7 @@ class mail_thread(osv.osv): if res_id: res_id = res_id.group(1) if res_id: - res_id = int(res_id) + res_id = res_id if model_pool.exists(cr, uid, res_id): if hasattr(model_pool, 'message_update'): model_pool.message_update(cr, uid, [res_id], msg, {}, context=context) @@ -558,7 +585,7 @@ class mail_thread(osv.osv): res_id = False if not res_id: res_id = create_record(msg) - #To forward the email to other followers + # To forward the email to other followers self.message_forward(cr, uid, model, [res_id], msg_txt, context=context) return res_id @@ -594,29 +621,34 @@ class mail_thread(osv.osv): fields = model_pool.fields_get(cr, uid, context=context) data = model_pool.default_get(cr, uid, fields, context=context) if 'name' in fields and not data.get('name'): - data['name'] = msg_dict.get('from','') + data['name'] = msg_dict.get('from', '') 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, vals={}, default_act=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``) - Additional behavior may be implemented by overriding this - method. + 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``) + Additional behavior may be implemented by overriding this + method. - :param dict msg_dict: a map containing the email details and + :param dict msg_dict: a map containing the email details and attachments. See ``message_process`` and ``mail.message.parse()`` for details. - :param dict context: if a ``thread_model`` value is present + :param dict vals: a dict containing values to update records + given their ids; if the dict is None or is + void, no write operation is performed. + :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 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): @@ -746,13 +778,8 @@ class mail_thread(osv.osv): self.message_append_note(cr, uid, [id], 'res.log', message, context=context) def message_append_note(self, cr, uid, ids, subject=None, body=None, parent_id=False, type='notification', subtype='html', context=None): - if subject is None: - if type == 'notification': - subject = _('System notification') - elif type == 'comment' and not parent_id: - subject = _('Comment') - elif type == 'comment' and parent_id: - subject = _('Reply') + if type in ['notification', 'reply']: + subject = None if subtype == 'html': body_html = body body_text = body @@ -765,51 +792,77 @@ class mail_thread(osv.osv): # Subscription mechanism #------------------------------------------------------ - def message_get_subscribers_ids(self, cr, uid, ids, context=None): + def message_get_subscribers(self, cr, uid, ids, context=None): + """ Returns the current document followers. Basically this method + checks in mail.subscription for entries with matching res_model, + res_id. + + :param get_ids: if set to True, return the ids of users; if set + to False, returns the result of a read in res.users + """ subscr_obj = self.pool.get('mail.subscription') subscr_ids = subscr_obj.search(cr, uid, ['&', ('res_model', '=', self._name), ('res_id', 'in', ids)], context=context) - subs = subscr_obj.read(cr, uid, subscr_ids, context=context) - return [sub['user_id'][0] for sub in subs] + return [sub['user_id'][0] for sub in subscr_obj.read(cr, uid, subscr_ids, ['user_id'], context=context)] - def message_get_subscribers(self, cr, uid, ids, context=None): - user_ids = self.message_get_subscribers_ids(cr, uid, ids, context=context) - users = self.pool.get('res.users').read(cr, uid, user_ids, fields=['id', 'name', 'avatar'], context=context) - return users + def message_read_subscribers(self, cr, uid, ids, fields=['id', 'name', 'avatar'], context=None): + """ Returns the current document followers as a read result. Used + mainly for Chatter having only one method to call to have + details about users. + """ + user_ids = self.message_get_subscribers(cr, uid, ids, context=context) + return self.pool.get('res.users').read(cr, uid, user_ids, fields=fields, context=context) def message_is_subscriber(self, cr, uid, ids, user_id = None, context=None): - users = self.message_get_subscribers(cr, uid, ids, context=context) + """ Check if uid or user_id (if set) is a subscriber to the current + document. + + :param user_id: if set, check is done on user_id; if not set + check is done on uid + """ sub_user_id = uid if user_id is None else user_id - if sub_user_id in [user['id'] for user in users]: + if sub_user_id in self.message_get_subscribers(cr, uid, ids, context=context): return True return False 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 + """ subscription_obj = self.pool.get('mail.subscription') to_subscribe_uids = [uid] if user_ids is None else user_ids create_ids = [] for id in ids: + already_subscribed_user_ids = self.message_get_subscribers(cr, uid, [id], context=context) for user_id in to_subscribe_uids: - if self.message_is_subscriber(cr, uid, [id], user_id=user_id, context=context): continue + if user_id in already_subscribed_user_ids: continue create_ids.append(subscription_obj.create(cr, uid, {'res_model': self._name, 'res_id': id, 'user_id': user_id}, context=context)) return create_ids def message_unsubscribe(self, cr, uid, ids, user_ids = None, context=None): - if not user_ids and not uid in self.message_get_subscribers_ids(cr, uid, ids, context=context): + """ Unsubscribe the user (or user_ids) from the current document. + + :param user_ids: a list of user_ids; if not set, subscribe + uid instead + """ + # Trying to unsubscribe somebody not in subscribers: returns False + # if special management is needed; allows to know that an automatically + # subscribed user tries to unsubscribe and allows to warn him + mail_thread_model = self.pool.get('mail.thread') + if not user_ids and not uid in mail_thread_model.message_get_subscribers(cr, uid, ids, context=context): return False subscription_obj = self.pool.get('mail.subscription') to_unsubscribe_uids = [uid] if user_ids is None else user_ids to_delete_sub_ids = subscription_obj.search(cr, uid, ['&', '&', ('res_model', '=', self._name), ('res_id', 'in', ids), ('user_id', 'in', to_unsubscribe_uids)], context=context) - subscription_obj.unlink(cr, uid, to_delete_sub_ids, context=context) - return True + return subscription_obj.unlink(cr, uid, to_delete_sub_ids, context=context) #------------------------------------------------------ # Notification API #------------------------------------------------------ def message_remove_pushed_notifications(self, cr, uid, ids, msg_ids, remove_childs=True, context=None): - if context is None: - context = {} notif_obj = self.pool.get('mail.notification') msg_obj = self.pool.get('mail.message') if remove_childs: @@ -819,4 +872,37 @@ class mail_thread(osv.osv): 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) + #------------------------------------------------------ + # 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 hasattr(obj, '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_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 hasattr(obj, '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) + + 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 hasattr(obj, '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: diff --git a/addons/mail/static/lib/jquery.expander/jquery.expander.js b/addons/mail/static/lib/jquery.expander/jquery.expander.js new file mode 100644 index 00000000000..91cc03db918 --- /dev/null +++ b/addons/mail/static/lib/jquery.expander/jquery.expander.js @@ -0,0 +1,385 @@ +/*! + * jQuery Expander Plugin v1.4.2 + * + * Date: Fri Mar 16 14:29:56 2012 EDT + * Requires: jQuery v1.3+ + * + * Copyright 2011, Karl Swedberg + * Dual licensed under the MIT and GPL licenses (just like jQuery): + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * + * + * +*/ + +(function($) { + $.expander = { + version: '1.4.2', + defaults: { + // the number of characters at which the contents will be sliced into two parts. + slicePoint: 100, + + // whether to keep the last word of the summary whole (true) or let it slice in the middle of a word (false) + preserveWords: true, + + // a threshold of sorts for whether to initially hide/collapse part of the element's contents. + // If after slicing the contents in two there are fewer words in the second part than + // the value set by widow, we won't bother hiding/collapsing anything. + widow: 4, + + // text displayed in a link instead of the hidden part of the element. + // clicking this will expand/show the hidden/collapsed text + expandText: 'read more', + expandPrefix: '… ', + + expandAfterSummary: false, + + // class names for summary element and detail element + summaryClass: 'summary', + detailClass: 'details', + + // class names for <span> around "read-more" link and "read-less" link + moreClass: 'read-more', + lessClass: 'read-less', + + // number of milliseconds after text has been expanded at which to collapse the text again. + // when 0, no auto-collapsing + collapseTimer: 0, + + // effects for expanding and collapsing + expandEffect: 'fadeIn', + expandSpeed: 250, + collapseEffect: 'fadeOut', + collapseSpeed: 200, + + // allow the user to re-collapse the expanded text. + userCollapse: true, + + // text to use for the link to re-collapse the text + userCollapseText: 'read less', + userCollapsePrefix: ' ', + + + // all callback functions have the this keyword mapped to the element in the jQuery set when .expander() is called + + onSlice: null, // function() {} + beforeExpand: null, // function() {}, + afterExpand: null, // function() {}, + onCollapse: null // function(byUser) {} + } + }; + + $.fn.expander = function(options) { + var meth = 'init'; + + if (typeof options == 'string') { + meth = options; + options = {}; + } + + var opts = $.extend({}, $.expander.defaults, options), + rSelfClose = /^<(?:area|br|col|embed|hr|img|input|link|meta|param).*>$/i, + rAmpWordEnd = opts.wordEnd || /(&(?:[^;]+;)?|[a-zA-Z\u00C0-\u0100]+)$/, + rOpenCloseTag = /<\/?(\w+)[^>]*>/g, + rOpenTag = /<(\w+)[^>]*>/g, + rCloseTag = /<\/(\w+)>/g, + rLastCloseTag = /(<\/[^>]+>)\s*$/, + rTagPlus = /^<[^>]+>.?/, + delayedCollapse; + + var methods = { + init: function() { + this.each(function() { + var i, l, tmp, newChar, summTagless, summOpens, summCloses, + lastCloseTag, detailText, detailTagless, + $thisDetails, $readMore, + openTagsForDetails = [], + closeTagsForsummaryText = [], + defined = {}, + thisEl = this, + $this = $(this), + $summEl = $([]), + o = $.meta ? $.extend({}, opts, $this.data()) : opts, + hasDetails = !!$this.find('.' + o.detailClass).length, + hasBlocks = !!$this.find('*').filter(function() { + var display = $(this).css('display'); + return (/^block|table|list/).test(display); + }).length, + el = hasBlocks ? 'div' : 'span', + detailSelector = el + '.' + o.detailClass, + moreSelector = 'span.' + o.moreClass, + expandSpeed = o.expandSpeed || 0, + allHtml = $.trim( $this.html() ), + allText = $.trim( $this.text() ), + summaryText = allHtml.slice(0, o.slicePoint); + + // bail out if we've already set up the expander on this element + if ( $.data(this, 'expander') ) { + return; + } + + $.data(this, 'expander', true); + + // determine which callback functions are defined + $.each(['onSlice','beforeExpand', 'afterExpand', 'onCollapse'], function(index, val) { + defined[val] = $.isFunction(o[val]); + }); + + // back up if we're in the middle of a tag or word + summaryText = backup(summaryText); + + // summary text sans tags length + summTagless = summaryText.replace(rOpenCloseTag, '').length; + + // add more characters to the summary, one for each character in the tags + while (summTagless < o.slicePoint) { + newChar = allHtml.charAt(summaryText.length); + if (newChar == '<') { + newChar = allHtml.slice(summaryText.length).match(rTagPlus)[0]; + } + summaryText += newChar; + summTagless++; + } + + summaryText = backup(summaryText, o.preserveWords); + + // separate open tags from close tags and clean up the lists + summOpens = summaryText.match(rOpenTag) || []; + summCloses = summaryText.match(rCloseTag) || []; + + // filter out self-closing tags + tmp = []; + $.each(summOpens, function(index, val) { + if ( !rSelfClose.test(val) ) { + tmp.push(val); + } + }); + summOpens = tmp; + + // strip close tags to just the tag name + l = summCloses.length; + for (i = 0; i < l; i++) { + summCloses[i] = summCloses[i].replace(rCloseTag, '$1'); + } + + // tags that start in summary and end in detail need: + // a). close tag at end of summary + // b). open tag at beginning of detail + $.each(summOpens, function(index, val) { + var thisTagName = val.replace(rOpenTag, '$1'); + var closePosition = $.inArray(thisTagName, summCloses); + if (closePosition === -1) { + openTagsForDetails.push(val); + closeTagsForsummaryText.push('</' + thisTagName + '>'); + + } else { + summCloses.splice(closePosition, 1); + } + }); + + // reverse the order of the close tags for the summary so they line up right + closeTagsForsummaryText.reverse(); + + // create necessary summary and detail elements if they don't already exist + if ( !hasDetails ) { + + // end script if there is no detail text or if detail has fewer words than widow option + detailText = allHtml.slice(summaryText.length); + detailTagless = $.trim( detailText.replace(rOpenCloseTag, '') ); + + if ( detailTagless === '' || detailTagless.split(/\s+/).length < o.widow ) { + return; + } + // otherwise, continue... + lastCloseTag = closeTagsForsummaryText.pop() || ''; + summaryText += closeTagsForsummaryText.join(''); + detailText = openTagsForDetails.join('') + detailText; + + } else { + // assume that even if there are details, we still need readMore/readLess/summary elements + // (we already bailed out earlier when readMore el was found) + // but we need to create els differently + + // remove the detail from the rest of the content + detailText = $this.find(detailSelector).remove().html(); + + // The summary is what's left + summaryText = $this.html(); + + // allHtml is the summary and detail combined (this is needed when content has block-level elements) + allHtml = summaryText + detailText; + + lastCloseTag = ''; + } + o.moreLabel = $this.find(moreSelector).length ? '' : buildMoreLabel(o); + + if (hasBlocks) { + detailText = allHtml; + } + summaryText += lastCloseTag; + + // onSlice callback + o.summary = summaryText; + o.details = detailText; + o.lastCloseTag = lastCloseTag; + + if (defined.onSlice) { + // user can choose to return a modified options object + // one last chance for user to change the options. sneaky, huh? + // but could be tricky so use at your own risk. + tmp = o.onSlice.call(thisEl, o); + + // so, if the returned value from the onSlice function is an object with a details property, we'll use that! + o = tmp && tmp.details ? tmp : o; + } + + // build the html with summary and detail and use it to replace old contents + var html = buildHTML(o, hasBlocks); + + $this.html( html ); + + // set up details and summary for expanding/collapsing + $thisDetails = $this.find(detailSelector); + $readMore = $this.find(moreSelector); + $thisDetails.hide(); + $readMore.find('a').unbind('click.expander').bind('click.expander', expand); + + $summEl = $this.find('div.' + o.summaryClass); + + if ( o.userCollapse && !$this.find('span.' + o.lessClass).length ) { + $this + .find(detailSelector) + .append('<span class="' + o.lessClass + '">' + o.userCollapsePrefix + '<a href="#">' + o.userCollapseText + '</a></span>'); + } + + $this + .find('span.' + o.lessClass + ' a') + .unbind('click.expander') + .bind('click.expander', function(event) { + event.preventDefault(); + clearTimeout(delayedCollapse); + var $detailsCollapsed = $(this).closest(detailSelector); + reCollapse(o, $detailsCollapsed); + if (defined.onCollapse) { + o.onCollapse.call(thisEl, true); + } + }); + + function expand(event) { + event.preventDefault(); + $readMore.hide(); + $summEl.hide(); + if (defined.beforeExpand) { + o.beforeExpand.call(thisEl); + } + + $thisDetails.stop(false, true)[o.expandEffect](expandSpeed, function() { + $thisDetails.css({zoom: ''}); + if (defined.afterExpand) {o.afterExpand.call(thisEl);} + delayCollapse(o, $thisDetails, thisEl); + }); + } + + }); // this.each + }, + destroy: function() { + if ( !this.data('expander') ) { + return; + } + this.removeData('expander'); + this.each(function() { + var $this = $(this), + o = $.meta ? $.extend({}, opts, $this.data()) : opts, + details = $this.find('.' + o.detailClass).contents(); + + $this.find('.' + o.moreClass).remove(); + $this.find('.' + o.summaryClass).remove(); + $this.find('.' + o.detailClass).after(details).remove(); + $this.find('.' + o.lessClass).remove(); + + }); + } + }; + + // run the methods (almost always "init") + if ( methods[meth] ) { + methods[ meth ].call(this); + } + + // utility functions + function buildHTML(o, blocks) { + var el = 'span', + summary = o.summary; + if ( blocks ) { + el = 'div'; + // if summary ends with a close tag, tuck the moreLabel inside it + if ( rLastCloseTag.test(summary) && !o.expandAfterSummary) { + summary = summary.replace(rLastCloseTag, o.moreLabel + '$1'); + } else { + // otherwise (e.g. if ends with self-closing tag) just add moreLabel after summary + // fixes #19 + summary += o.moreLabel; + } + + // and wrap it in a div + summary = '<div class="' + o.summaryClass + '">' + summary + '</div>'; + } else { + summary += o.moreLabel; + } + + return [ + summary, + '<', + el + ' class="' + o.detailClass + '"', + '>', + o.details, + '</' + el + '>' + ].join(''); + } + + function buildMoreLabel(o) { + var ret = '<span class="' + o.moreClass + '">' + o.expandPrefix; + ret += '<a href="#">' + o.expandText + '</a></span>'; + return ret; + } + + function backup(txt, preserveWords) { + if ( txt.lastIndexOf('<') > txt.lastIndexOf('>') ) { + txt = txt.slice( 0, txt.lastIndexOf('<') ); + } + if (preserveWords) { + txt = txt.replace(rAmpWordEnd,''); + } + + return $.trim(txt); + } + + function reCollapse(o, el) { + el.stop(true, true)[o.collapseEffect](o.collapseSpeed, function() { + var prevMore = el.prev('span.' + o.moreClass).show(); + if (!prevMore.length) { + el.parent().children('div.' + o.summaryClass).show() + .find('span.' + o.moreClass).show(); + } + }); + } + + function delayCollapse(option, $collapseEl, thisEl) { + if (option.collapseTimer) { + delayedCollapse = setTimeout(function() { + reCollapse(option, $collapseEl); + if ( $.isFunction(option.onCollapse) ) { + option.onCollapse.call(thisEl, false); + } + }, option.collapseTimer); + } + } + + return this; + }; + + // plugin defaults + $.fn.expander.defaults = $.expander.defaults; +})(jQuery); diff --git a/addons/mail/static/src/css/mail.css b/addons/mail/static/src/css/mail.css index 2421b28fbcb..0cf932f9a25 100644 --- a/addons/mail/static/src/css/mail.css +++ b/addons/mail/static/src/css/mail.css @@ -314,8 +314,8 @@ text-align: justify; } -.openerp .oe_mail_msg_tail { - display: none; +.openerp .oe_mail_msg_body span.oe_mail_msg_tail { + display: inline; } /* Read more/less link */ diff --git a/addons/mail/static/src/css/mail_group.css b/addons/mail/static/src/css/mail_group.css index 90261499753..048b7a06415 100644 --- a/addons/mail/static/src/css/mail_group.css +++ b/addons/mail/static/src/css/mail_group.css @@ -1,3 +1,40 @@ +/* ------------------------------ */ +/* Group Form */ +/* ------------------------------ */ + +/* Resize sheet width */ +.openerp .oe_form_sheetbg.openerp_mail_group_sheet { + min-height: 0px; + max-height: none; +} + +.openerp .oe_form_sheetbg.openerp_mail_group_sheet .oe_form_sheet { + min-height: 0px; + max-height: none; + padding: 0px 18px; + max-width: 80%; +} + +/* Resize footer width */ +.openerp .oe_form footer.openerp_mail_group_footer { + max-width: 80%; +} + +/* Resize group logo */ +.openerp .oe_form_sheetbg.openerp_mail_group_sheet .oe_form_field_image > img { + max-width: 100px; + max-height: 100px; +} + +/* Resize group description */ +.openerp .oe_form_sheetbg.openerp_mail_group_sheet .oe_form_field_text > textarea { + height: 40px; +} + +/* ------------------------------ */ +/* Group Kanban */ +/* ------------------------------ */ + .oe_group_vignette { padding: 8px 0; min-height: 100px; @@ -71,9 +108,3 @@ .oe_group_details li { margin: 2px 0; } - -.oe_group_description { -} - -.oe_kanban_record div.true { -} diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js index 5633a4bdae0..da54130d093 100644 --- a/addons/mail/static/src/js/mail.js +++ b/addons/mail/static/src/js/mail.js @@ -302,17 +302,20 @@ openerp.mail = function(session) { record.body = this.do_clean_text(record.body); record.body = this.do_replace_internal_links(record.body); - // split for see more - var split = this.do_truncate_string(record.body, this.params.msg_more_limit); - record.body_head = split[0]; - record.body_tail = split[1]; - // format date according to the user timezone record.date = session.web.format_value(record.date, {type:"datetime"}); - var rendered = session.web.qweb.render('mail.Thread.message', {'record': record, 'thread': this, 'params': this.params, 'display': this.display}); - $( rendered).appendTo(this.$element.children('div.oe_mail_thread_display:first')); + $(rendered).appendTo(this.$element.children('div.oe_mail_thread_display:first')); + // expand feature + this.$element.find('div.oe_mail_msg_body:last').expander({ + slicePoint: this.params.msg_more_limit, + expandText: 'see more', + userCollapseText: 'see less', + detailClass: 'oe_mail_msg_tail', + moreClass: 'oe_mail_expand', + lesClass: 'oe_mail_reduce', + }); }, /** @@ -447,20 +450,6 @@ openerp.mail = function(session) { return this.session.prefix + '/web/binary/image?session_id=' + this.session.session_id + '&model=' + model + '&field=' + field + '&id=' + (id || ''); }, - /** - * @param {String} string to truncate - * @param {Number} max number of chars to display - * @returns {String} truncated string - */ - do_truncate_string: function(string, max_length) { - // multiply by 1.2: prevent truncating an just too little long string - if (string.length <= (max_length * 1.2)) { - return [string, ""]; - } else { - return [string.slice(0, max_length), string.slice(max_length)]; - } - }, - /** Removes html tags, except b, em, br */ do_clean_text: function (string) { var html = $('<div/>').text(string.replace(/\s+/g, ' ')).html().replace(new RegExp('<(/)?(b|em|br|br /)\\s*>', 'gi'), '<$1$2>'); @@ -565,7 +554,7 @@ openerp.mail = function(session) { }, fetch_subscribers: function () { - return this.ds.call('message_get_subscribers', [[this.view.datarecord.id]]).then(this.proxy('display_subscribers')); + return this.ds.call('message_read_subscribers', [[this.view.datarecord.id]]).then(this.proxy('display_subscribers')); }, display_subscribers: function (records) { diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml index 34445c48f8d..2eb3a390933 100644 --- a/addons/mail/static/src/xml/mail.xml +++ b/addons/mail/static/src/xml/mail.xml @@ -91,10 +91,7 @@ </div> <div class="oe_mail_msg_body"> <a href="#" data-res-model='res.users' t-attf-data-res-id='{record.user_id[0]}' class="oe_mail_internal_link"><t t-raw="record.user_id[1]"/></a> - <t t-raw="record.body_head"/> - <t t-if="record.body_tail"><a href="#" class="oe_mail_msg_more">... See more</a> - <span class="oe_mail_msg_tail"><t t-raw="record.body_tail"/></span> - </t> + <t t-raw="record.body"/> </div> <ul class="oe_mail_msg_footer"> <li><span t-att-title="record.date"><t t-raw="record.timerelative"/></span></li> diff --git a/addons/mail/wizard/mail_compose_message_view.xml b/addons/mail/wizard/mail_compose_message_view.xml index 99eb7192738..3da63fb1adc 100644 --- a/addons/mail/wizard/mail_compose_message_view.xml +++ b/addons/mail/wizard/mail_compose_message_view.xml @@ -12,18 +12,18 @@ or <button string="Cancel" class="oe_link" special="cancel" /> </header> - <group col="6" colspan="4"> + <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" 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"/> - <field name="references" invisible="1"/> - <field name="message_id" invisible="1"/> + <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"/> </group> <notebook colspan="4"> <page string="Body"> diff --git a/addons/membership/membership.py b/addons/membership/membership.py index 543f9636f7c..dc4eadbada9 100644 --- a/addons/membership/membership.py +++ b/addons/membership/membership.py @@ -141,7 +141,7 @@ class membership_line(osv.osv): _name = 'membership.membership_line' _columns = { 'partner': fields.many2one('res.partner', 'Partner', ondelete='cascade', select=1), - 'membership_id': fields.many2one('product.product', string="Membership Product", required=True), + 'membership_id': fields.many2one('product.product', string="Membership", required=True), 'date_from': fields.date('From', readonly=True), 'date_to': fields.date('To', readonly=True), 'date_cancel': fields.date('Cancel date'), diff --git a/addons/membership/membership_view.xml b/addons/membership/membership_view.xml index 15645294bb7..0f2ad6836c8 100644 --- a/addons/membership/membership_view.xml +++ b/addons/membership/membership_view.xml @@ -241,8 +241,11 @@ <group> <group> <field name="free_member"/> - <field colspan="4" name="membership_state"/> - <button name="%(action_membership_invoice_view)d" type="action" string="Join Membership" attrs="{'readonly':[('free_member','=',True)]}" icon="gtk-apply"/> + <label for="membership_state"/> + <div> + <field name="membership_state" class="oe_inline"/> + <button name="%(action_membership_invoice_view)d" type="action" string="Join Membership" attrs="{'readonly':[('free_member','=',True)]}" class="oe_inline oe_right"/> + </div> </group> <group> <field name="associate_member" attrs="{'readonly':[('membership_state','!=', 'none')]}"/> @@ -254,7 +257,7 @@ <field name="member_lines" nolabel="1" colspan="4" readonly="1"> <tree string="Memberships"> <field name="date"/> - <field name="membership_id" string="Membership"/> + <field name="membership_id"/> <field name="member_price"/> <field name="account_invoice_id"/> <field name="state"/> diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py index 22d493a7d48..c4a4d8df895 100644 --- a/addons/mrp/mrp.py +++ b/addons/mrp/mrp.py @@ -1033,20 +1033,14 @@ class mrp_production(osv.osv): # --------------------------------------------------- # OpenChatter methods and notifications # --------------------------------------------------- - - def get_needaction_user_ids(self, cr, uid, ids, context=None): - result = dict.fromkeys(ids, []) - for obj in self.browse(cr, uid, ids, context=context): - if obj.state == 'draft' and obj.user_id: - result[obj.id] = [obj.user_id.id] - return result def message_get_subscribers(self, cr, uid, ids, context=None): - sub_ids = self.message_get_subscribers_ids(cr, uid, ids, context=context); + """ Override to add responsible user. """ + user_ids = super(mrp_production, self).message_get_subscribers(cr, uid, ids, context=context) for obj in self.browse(cr, uid, ids, context=context): - if obj.user_id: - sub_ids.append(obj.user_id.id) - return self.pool.get('res.users').read(cr, uid, sub_ids, context=context) + if obj.user_id and not obj.user_id.id in user_ids: + user_ids.append(obj.user_id.id) + return user_ids 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) diff --git a/addons/portal/wizard/share_wizard.py b/addons/portal/wizard/share_wizard.py index d642be7b698..ca950a61b08 100644 --- a/addons/portal/wizard/share_wizard.py +++ b/addons/portal/wizard/share_wizard.py @@ -28,7 +28,7 @@ UID_ROOT = 1 SHARED_DOCS_MENU = "Documents" SHARED_DOCS_CHILD_MENU = "Shared Documents" -class share_wizard_portal(osv.osv_memory): +class share_wizard_portal(osv.TransientModel): """Inherited share wizard to automatically create appropriate menus in the selected portal upon sharing with a portal group.""" _inherit = "share.wizard" diff --git a/addons/portal/wizard/share_wizard_view.xml b/addons/portal/wizard/share_wizard_view.xml index 2f9d55c1f0d..cf294285f4f 100644 --- a/addons/portal/wizard/share_wizard_view.xml +++ b/addons/portal/wizard/share_wizard_view.xml @@ -8,7 +8,7 @@ <field name="type">form</field> <field name="inherit_id" ref="share.share_step1_form"/> <field name="arch" type="xml"> - <xpath expr="/form/field[@name='user_type']" position="attributes"> + <xpath expr="//field[@name='user_type']" position="attributes"> <attribute name='invisible'>0</attribute> <attribute name='colspan'>4</attribute> </xpath> @@ -40,11 +40,11 @@ <xpath expr="//field[@name='result_line_ids']//field[@name='login']" position="after"> <field name="share_url" groups="portal.group_portal_manager"/> </xpath> - <xpath expr="//field[@name='result_line_ids']//field[@name='login']" position="after"> - <field name="share_url" colspan="4" groups="portal.group_portal_manager"/> - </xpath> <xpath expr="//field[@name='result_line_ids']" position="after"> - <field colspan="4" name="share_root_url" groups="portal.group_portal_manager"/> + <newline/> + <group string="Details"> + <field name="share_root_url" groups="portal.group_portal_manager"/> + </group> </xpath> </field> </record> diff --git a/addons/product/product_view.xml b/addons/product/product_view.xml index f9fd2b78986..0fb0b0559f9 100644 --- a/addons/product/product_view.xml +++ b/addons/product/product_view.xml @@ -312,12 +312,26 @@ <field name="type">form</field> <field name="arch" type="xml"> <form string="Product Categories" version="7.0"> - <group col="4"> - <field name="name"/> - <field name="parent_id"/> - <field name="sequence" invisible="1"/> - <field name="type"/> - </group> + <sheet> + <div class="oe_title"> + <h1> + <label for="name" class="oe_edit_only"/> + <field name="name"/> + </h1> + </div> + <group> + <group> + <field name="type"/> + </group> + <group> + <field name="parent_id"/> + </group> + </group> + <group name="basic"> + <group name="account_property" string="Account Properties"/> + <group name="account_stock_property" string="Account Stock Properties"/> + </group> + </sheet> </form> </field> </record> @@ -409,12 +423,12 @@ <field name="arch" type="xml"> <form string="Units of Measure" version="7.0"> <group> - <group string="Unit of Measure Properties"> + <group> <field name="name"/> <field name="category_id"/> <field name="active"/> </group> - <group string="Ratio & Precision"> + <group> <field name="uom_type" on_change="onchange_type(uom_type)"/> <field name="rounding"/> <field name="factor" attrs="{'invisible':[('uom_type','!=','smaller')]}"/> diff --git a/addons/product_manufacturer/product_manufacturer_view.xml b/addons/product_manufacturer/product_manufacturer_view.xml index 4a19996af66..655dc841d17 100644 --- a/addons/product_manufacturer/product_manufacturer_view.xml +++ b/addons/product_manufacturer/product_manufacturer_view.xml @@ -10,19 +10,19 @@ <field name="arch" type="xml"> <notebook position="inside"> <page string="Manufacturer"> - <separator string="Manufacturer" colspan="4" /> - <field name="manufacturer" /> - <newline/> - <field name="manufacturer_pname"/> - <field name="manufacturer_pref"/> - - <separator string="Attributes" colspan="4"/> - <field name="attribute_ids" colspan="4" nolabel="1"> - <tree string="Product Attributes" editable="bottom"> - <field name="name"/> - <field name="value"/> - </tree> - </field> + <group string="Manufacturer"> + <field name="manufacturer" /> + <field name="manufacturer_pname"/> + <field name="manufacturer_pref"/> + </group> + <group string="Attributes"> + <field name="attribute_ids" colspan="4" nolabel="1"> + <tree string="Product Attributes" editable="bottom"> + <field name="name"/> + <field name="value"/> + </tree> + </field> + </group> </page> </notebook> </field> diff --git a/addons/project/project.py b/addons/project/project.py index 05a81c99514..487daa834bb 100644 --- a/addons/project/project.py +++ b/addons/project/project.py @@ -480,21 +480,14 @@ def Project(): # ------------------------------------------------ # OpenChatter methods and notifications # ------------------------------------------------ - - def get_needaction_user_ids(self, cr, uid, ids, context=None): - result = dict.fromkeys(ids) - for obj in self.browse(cr, uid, ids, context=context): - result[obj.id] = [] - if obj.state == 'draft' and obj.user_id: - result[obj.id] = [obj.user_id.id] - return result def message_get_subscribers(self, cr, uid, ids, context=None): - sub_ids = self.message_get_subscribers_ids(cr, uid, ids, context=context); + """ Override to add responsible user. """ + user_ids = super(project, self).message_get_subscribers(cr, uid, ids, context=context) for obj in self.browse(cr, uid, ids, context=context): - if obj.user_id: - sub_ids.append(obj.user_id.id) - return self.pool.get('res.users').read(cr, uid, sub_ids, context=context) + if obj.user_id and not obj.user_id.id in user_ids: + user_ids.append(obj.user_id.id) + return user_ids def create(self, cr, uid, vals, context=None): obj_id = super(project, self).create(cr, uid, vals, context=context) @@ -1145,20 +1138,26 @@ class task(base_stage, osv.osv): return 'Task' def get_needaction_user_ids(self, cr, uid, ids, context=None): - result = dict.fromkeys(ids, []) + """ Returns the user_ids that have to perform an action. + Add to the previous results given by super the document responsible + when in draft mode. + :return: dict { record_id: [user_ids], } + """ + result = super(task, self).get_needaction_user_ids(cr, uid, ids, context=context) for obj in self.browse(cr, uid, ids, context=context): if obj.state == 'draft' and obj.user_id: - result[obj.id] = [obj.user_id.id] + result[obj.id].append(obj.user_id.id) return result def message_get_subscribers(self, cr, uid, ids, context=None): - sub_ids = self.message_get_subscribers_ids(cr, uid, ids, context=context); + """ Override to add responsible user and project manager. """ + user_ids = super(task, self).message_get_subscribers(cr, uid, ids, context=context) for obj in self.browse(cr, uid, ids, context=context): - if obj.user_id: - sub_ids.append(obj.user_id.id) - if obj.manager_id: - sub_ids.append(obj.manager_id.id) - return self.pool.get('res.users').read(cr, uid, sub_ids, context=context) + if obj.user_id and not obj.user_id.id in user_ids: + user_ids.append(obj.user_id.id) + if obj.manager_id and not obj.manager_id.id in user_ids: + user_ids.append(obj.manager_id.id) + return user_ids def stage_set_send_note(self, cr, uid, ids, stage_id, context=None): """ Override of the (void) default notification method. """ diff --git a/addons/project/project_view.xml b/addons/project/project_view.xml index e58aad743dd..cf501740456 100644 --- a/addons/project/project_view.xml +++ b/addons/project/project_view.xml @@ -21,6 +21,43 @@ view_mode="kanban,tree,form,calendar,graph" view_type="form"/> + <!-- Project Read/Unread actions --> + <record id="actions_server_project_unread" model="ir.actions.server"> + <field name="name">Mark unread</field> + <field name="condition">True</field> + <field name="type">ir.actions.server</field> + <field name="model_id" ref="model_project_project"/> + <field name="state">code</field> + <field name="code">self.message_check_and_set_unread(cr, uid, context.get('active_ids'), context=context)</field> + </record> + <record id="action_project_unread" model="ir.values"> + <field name="name">action_project_unread</field> + <field name="action_id" ref="actions_server_project_unread"/> + <field name="value" eval="'ir.actions.server,' + str(ref('actions_server_project_unread'))" /> + <field name="key">action</field> + <field name="model_id" ref="model_project_project" /> + <field name="model">project.project</field> + <field name="key2">client_action_multi</field> + </record> + + <record id="actions_server_project_read" model="ir.actions.server"> + <field name="name">Mark read</field> + <field name="condition">True</field> + <field name="type">ir.actions.server</field> + <field name="model_id" ref="model_project_project"/> + <field name="state">code</field> + <field name="code">self.message_check_and_set_read(cr, uid, context.get('active_ids'), context=context)</field> + </record> + <record id="action_project_read" model="ir.values"> + <field name="name">action_project_read</field> + <field name="action_id" ref="actions_server_project_read"/> + <field name="value" eval="'ir.actions.server,' + str(ref('actions_server_project_read'))" /> + <field name="key">action</field> + <field name="model_id" ref="model_project_project" /> + <field name="model">project.project</field> + <field name="key2">client_action_multi</field> + </record> + <!-- Project --> <record id="edit_project" model="ir.ui.view"> <field name="name">project.project.form</field> @@ -126,6 +163,11 @@ <group> <field name="complete_name" string="Project Name"/> <separator orientation="vertical"/> + <filter icon="terp-mail-message-new" + string="Inbox" help="Unread messages" + name="needaction_pending" + domain="[('needaction_pending','=',True)]"/> + <separator orientation="vertical"/> <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"/> <filter icon="gtk-media-pause" string="Template" name="Template" domain="[('state', '=','template')]" help="Templates of Projects"/> @@ -308,6 +350,44 @@ </record> <!-- Task --> + + <!-- Task Read/Unread actions --> + <record id="actions_server_project_task_unread" model="ir.actions.server"> + <field name="name">Mark unread</field> + <field name="condition">True</field> + <field name="type">ir.actions.server</field> + <field name="model_id" ref="model_project_task"/> + <field name="state">code</field> + <field name="code">self.message_check_and_set_unread(cr, uid, context.get('active_ids'), context=context)</field> + </record> + <record id="action_project_task_unread" model="ir.values"> + <field name="name">action_project_task_unread</field> + <field name="action_id" ref="actions_server_project_task_unread"/> + <field name="value" eval="'ir.actions.server,' + str(ref('actions_server_project_task_unread'))" /> + <field name="key">action</field> + <field name="model_id" ref="model_project_task" /> + <field name="model">project.task</field> + <field name="key2">client_action_multi</field> + </record> + + <record id="actions_server_project_task_read" model="ir.actions.server"> + <field name="name">Mark read</field> + <field name="condition">True</field> + <field name="type">ir.actions.server</field> + <field name="model_id" ref="model_project_task"/> + <field name="state">code</field> + <field name="code">self.message_check_and_set_read(cr, uid, context.get('active_ids'), context=context)</field> + </record> + <record id="action_project_task_read" model="ir.values"> + <field name="name">action_project_task_read</field> + <field name="action_id" ref="actions_server_project_task_read"/> + <field name="value" eval="'ir.actions.server,' + str(ref('actions_server_project_task_read'))" /> + <field name="key">action</field> + <field name="model_id" ref="model_project_task" /> + <field name="model">project.task</field> + <field name="key2">client_action_multi</field> + </record> + <record id="view_task_form2" model="ir.ui.view"> <field name="name">project.task.form</field> <field name="model">project.task</field> @@ -440,6 +520,8 @@ <field name="kanban_state"/> <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"/> <templates> <t t-name="kanban-box"> <div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click"> @@ -568,6 +650,11 @@ <group> <field name="name" string="Tasks"/> <separator orientation="vertical"/> + <filter icon="terp-mail-message-new" + string="Inbox" help="Unread messages" + name="needaction_pending" + domain="[('needaction_pending','=',True)]"/> + <separator orientation="vertical"/> <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"/> <filter string="Pending" domain="[('state','=','pending')]" context="{'show_delegated':False}" help="Pending Tasks" icon="terp-gtk-media-pause"/> diff --git a/addons/project_issue/project_issue.py b/addons/project_issue/project_issue.py index 009f2b8ecf7..fea7a64d2b7 100644 --- a/addons/project_issue/project_issue.py +++ b/addons/project_issue/project_issue.py @@ -467,87 +467,68 @@ class project_issue(base_stage, osv.osv): # ------------------------------------------------------- def message_new(self, cr, uid, msg, custom_values=None, context=None): - """Automatically called when new email message arrives""" - if context is None: - context = {} - subject = msg.get('subject') or _('No Title') - body = msg.get('body_text') - msg_from = msg.get('from') - priority = msg.get('priority') - vals = { - 'name': subject, - 'email_from': msg_from, + """ 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 = {} + if context is None: context = {} + context['state_to'] = 'draft' + + custom_values.update({ + 'name': msg.get('subject') or _("No Subject"), + 'description': msg.get('body_text'), + 'email_from': msg.get('from'), 'email_cc': msg.get('cc'), - 'description': body, 'user_id': False, - } - if priority: - vals['priority'] = priority - vals.update(self.message_partner_by_email(cr, uid, msg_from)) - context.update({'state_to' : 'draft'}) - - if custom_values and isinstance(custom_values, dict): - vals.update(custom_values) + }) + 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 = self.create(cr, uid, vals, context) - self.message_append_dict(cr, uid, [res_id], msg, 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) return res_id - def message_update(self, cr, uid, ids, msg, vals=None, default_act='pending', context=None): - if vals is None: - vals = {} - + 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 document according to the email. + """ if isinstance(ids, (str, int, long)): ids = [ids] + if update_vals is None: update_vals = {} - vals.update({ - 'description': msg['body_text'] - }) - if msg.get('priority', False): - vals['priority'] = msg.get('priority') - + # Update doc values according to the message + update_vals['description'] = msg.get('body_text', '') + if msg.get('priority'): + update_vals['priority'] = msg.get('priority') + # Parse 'body_text' to find values to update maps = { 'cost': 'planned_cost', 'revenue': 'planned_revenue', - 'probability': 'probability' + 'probability': 'probability', } - - # Reassign the 'open' state to the case if this one is in pending or done - for record in self.browse(cr, uid, ids, context=context): - if record.state in ('pending', 'done'): - self.case_set(cr, uid, ids, 'open', {}, context=context) - - vls = { } - for line in msg['body_text'].split('\n'): + for line in msg.get('body_text', '').split('\n'): line = line.strip() res = tools.misc.command_re.match(line) if res and maps.get(res.group(1).lower(), False): key = maps.get(res.group(1).lower()) - vls[key] = res.group(2).lower() + update_vals[key] = res.group(2).lower() - vals.update(vls) - res = self.write(cr, uid, ids, vals) - self.message_append_dict(cr, uid, ids, msg, context=context) - return res + return super(project_issue, self).message_update(cr, uid, ids, update_vals=update_vals, context=context) # ------------------------------------------------------- # OpenChatter methods and notifications # ------------------------------------------------------- - def get_needaction_user_ids(self, cr, uid, ids, context=None): - result = dict.fromkeys(ids, []) - for obj in self.browse(cr, uid, ids, context=context): - if obj.state == 'draft' and obj.user_id: - result[obj.id] = [obj.user_id.id] - return result - def message_get_subscribers(self, cr, uid, ids, context=None): - sub_ids = self.message_get_subscribers_ids(cr, uid, ids, context=context); + """ Override to add responsible user. """ + user_ids = super(project_issue, self).message_get_subscribers(cr, uid, ids, context=context) for obj in self.browse(cr, uid, ids, context=context): - if obj.user_id: - sub_ids.append(obj.user_id.id) - return self.pool.get('res.users').read(cr, uid, sub_ids, context=context) + if obj.user_id and not obj.user_id.id in user_ids: + user_ids.append(obj.user_id.id) + return user_ids def stage_set_send_note(self, cr, uid, ids, stage_id, context=None): """ Override of the (void) default notification method. """ diff --git a/addons/project_issue/project_issue_demo.xml b/addons/project_issue/project_issue_demo.xml index 7242975fdb3..8f582dbe015 100644 --- a/addons/project_issue/project_issue_demo.xml +++ b/addons/project_issue/project_issue_demo.xml @@ -227,5 +227,10 @@ <field name="stage_id" ref="project.project_tt_specification"/> </record> + <!-- Call Function to set some issues as Unread --> + <function model="project.issue" name="message_mark_as_unread" + eval="[ ref('crm_case_improvereportsinhrms0'), ref('crm_case_createnewobject0')], {}" + /> + </data> </openerp> diff --git a/addons/project_issue/project_issue_view.xml b/addons/project_issue/project_issue_view.xml index 87a5a4f1f40..cdcea2cbb62 100644 --- a/addons/project_issue/project_issue_view.xml +++ b/addons/project_issue/project_issue_view.xml @@ -4,6 +4,43 @@ <menuitem id="menu_project_confi" name="Issues" parent="base.menu_definitions" sequence="2"/> + <!-- Project issue Read/Unread actions --> + <record id="actions_server_project_issue_unread" model="ir.actions.server"> + <field name="name">Mark unread</field> + <field name="condition">True</field> + <field name="type">ir.actions.server</field> + <field name="model_id" ref="model_project_issue"/> + <field name="state">code</field> + <field name="code">self.message_check_and_set_unread(cr, uid, context.get('active_ids'), context=context)</field> + </record> + <record id="action_project_issue_unread" model="ir.values"> + <field name="name">action_project_issue_unread</field> + <field name="action_id" ref="actions_server_project_issue_unread"/> + <field name="value" eval="'ir.actions.server,' + str(ref('actions_server_project_issue_unread'))" /> + <field name="key">action</field> + <field name="model_id" ref="model_project_issue" /> + <field name="model">project.issue</field> + <field name="key2">client_action_multi</field> + </record> + + <record id="actions_server_project_issue_read" model="ir.actions.server"> + <field name="name">Mark read</field> + <field name="condition">True</field> + <field name="type">ir.actions.server</field> + <field name="model_id" ref="model_project_issue"/> + <field name="state">code</field> + <field name="code">self.message_check_and_set_read(cr, uid, context.get('active_ids'), context=context)</field> + </record> + <record id="action_project_issue_read" model="ir.values"> + <field name="name">action_project_issue_read</field> + <field name="action_id" ref="actions_server_project_issue_read"/> + <field name="value" eval="'ir.actions.server,' + str(ref('actions_server_project_issue_read'))" /> + <field name="key">action</field> + <field name="model_id" ref="model_project_issue" /> + <field name="model">project.issue</field> + <field name="key2">client_action_multi</field> + </record> + <record model="ir.ui.view" id="project_issue_version_search_view"> <field name="name">Issue Version</field> <field name="model">project.issue.version</field> @@ -159,9 +196,13 @@ <group> <field name="name" string="Issue" filter_domain="['|', '|',('partner_id','ilike',self),('email_from','ilike',self),('name','ilike',self)]"/> <separator orientation="vertical"/> + <filter icon="terp-mail-message-new" + string="Inbox" help="Unread messages" + name="needaction_pending" + domain="[('needaction_pending','=',True)]"/> + <separator orientation="vertical"/> <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"/> - <filter string="Pending" domain="[('state','=','pending')]" help="Pending Issues" icon="terp-gtk-media-pause"/> <filter string="Unassigned Issues" domain="[('user_id','=',False)]" help="Unassigned Issues" icon="terp-personal-" /> <separator orientation="vertical"/> <field name="user_id"/> diff --git a/addons/project_long_term/project_long_term_view.xml b/addons/project_long_term/project_long_term_view.xml index ec575a514c1..a98e9088402 100644 --- a/addons/project_long_term/project_long_term_view.xml +++ b/addons/project_long_term/project_long_term_view.xml @@ -152,23 +152,23 @@ <field name="state" widget="statusbar" statusbar_visible="draft,open,done" statusbar_colors='{"pending":"blue"}'/> </header> <sheet> - <label for="name" class="oe_edit_only"/> - <h1><field name="name"/></h1> - <label for="project_id" class="oe_edit_only"/> - <h2> - <field name="project_id" on_change="onchange_project(project_id)" colspan="3"/> - </h2> + <div class="oe_title"> + <label for="name" class="oe_edit_only"/> + <h1><field name="name"/></h1> + <label for="project_id" class="oe_edit_only"/> + <h2> + <field name="project_id" on_change="onchange_project(project_id)" colspan="3"/> + </h2> + </div> <group> - <group> - <field name="date_start"/> - <field name="date_end"/> - </group><group> - <label for="duration"/> - <div> - <field name="duration" class="oe_inline"/> - <field name="product_uom"/> - </div> - </group> + <label for="date_start" string="Duration"/> + <div> + <div> + <field name="duration" class="oe_inline"/> + <field name="product_uom" class="oe_inline"/> + </div> + <field name="date_start" class="oe_inline"/><label string=" - " class="oe_inline"/><field name="date_end" class="oe_inline"/> + </div> </group> <notebook> <page string="Planning of Users"> diff --git a/addons/project_mailgate/project_mailgate.py b/addons/project_mailgate/project_mailgate.py index a36366f450e..0ca2687b7f0 100644 --- a/addons/project_mailgate/project_mailgate.py +++ b/addons/project_mailgate/project_mailgate.py @@ -19,30 +19,35 @@ # ############################################################################## +import binascii from osv import fields, osv from tools.translate import _ import tools -import binascii - class project_tasks(osv.osv): _inherit = 'project.task' def message_new(self, cr, uid, msg, custom_values=None, context=None): - res_id = super(project_tasks,self).message_new(cr, uid, msg, custom_values=custom_values, context=context) - subject = msg.get('subject') - msg_from = msg.get('from') - data = { + """ 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, - } - data.update(self.message_partner_by_email(cr, uid, msg_from)) - self.write(cr, uid, [res_id], data, context) - return res_id - - def message_update(self, cr, uid, ids, msg, data={}, default_act='pending'): - act = 'do_'+default_act + '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', } @@ -54,17 +59,15 @@ class project_tasks(osv.osv): field = maps.get(match) if field: try: - data[field] = float(res.group(2).lower()) + 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() - - self.write(cr, uid, ids, data, context=context) - getattr(self,act)(cr, uid, ids, context=context) - self.message_append_note(cr, uid, [res_id], body=msg, context=context) - return True + 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) @@ -74,5 +77,5 @@ class project_tasks(osv.osv): followers[task.id] = filter(None, task_followers) return followers -project_tasks() + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index 775aebfaa8f..e74ca83a92b 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -733,10 +733,10 @@ class purchase_order(osv.osv): # -------------------------------------- def get_needaction_user_ids(self, cr, uid, ids, context=None): - result = dict.fromkeys(ids, []) + 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] = [obj.validator.id] + result[obj.id].append(obj.validator.id) return result def create_send_note(self, cr, uid, ids, context=None): diff --git a/addons/purchase/purchase_view.xml b/addons/purchase/purchase_view.xml index d89827d4812..d5a9b93f9ea 100644 --- a/addons/purchase/purchase_view.xml +++ b/addons/purchase/purchase_view.xml @@ -52,10 +52,10 @@ action="base.action_partner_category_form" id="menu_partner_categories_in_form" name="Partner Categories" parent="purchase.menu_purchase_partner_cat" groups="base.group_no_one"/> - <!--supplier menu--> + <!--supplier menu--> <menuitem id="base.menu_procurement_management_supplier_name" name="Suppliers" - parent="menu_procurement_management" - action="base.action_partner_supplier_form" sequence="15"/> + parent="menu_procurement_management" + action="base.action_partner_supplier_form" sequence="15"/> <!--Inventory control--> <menuitem id="menu_procurement_management_inventory" name="Receive Products" @@ -65,9 +65,9 @@ <menuitem action="stock.action_reception_picking_move" id="menu_action_picking_tree_in_move" parent="menu_procurement_management_inventory" sequence="11"/> - <!--Invoice control--> - <menuitem id="menu_procurement_management_invoice" name="Invoice Control" - parent="base.menu_purchase_root" sequence="6"/> + <!--Invoice control--> + <menuitem id="menu_procurement_management_invoice" name="Invoice Control" + parent="base.menu_purchase_root" sequence="6"/> <record id="action_invoice_pending" model="ir.actions.act_window"> <field name="name">Based on Draft Invoices</field> @@ -101,6 +101,43 @@ <menuitem name="Products" id="menu_procurement_partner_contact_form" action="product.product_normal_action_puchased" parent="menu_procurement_management_product"/> + <!-- Purchase order Read/Unread actions --> + <record id="actions_server_purchase_order_unread" model="ir.actions.server"> + <field name="name">Mark unread</field> + <field name="condition">True</field> + <field name="type">ir.actions.server</field> + <field name="model_id" ref="model_purchase_order"/> + <field name="state">code</field> + <field name="code">self.message_check_and_set_unread(cr, uid, context.get('active_ids'), context=context)</field> + </record> + <record id="action_purchase_order_unread" model="ir.values"> + <field name="name">action_purchase_order_unread</field> + <field name="action_id" ref="actions_server_purchase_order_unread"/> + <field name="value" eval="'ir.actions.server,' + str(ref('actions_server_purchase_order_unread'))" /> + <field name="key">action</field> + <field name="model_id" ref="model_purchase_order" /> + <field name="model">purchase.order</field> + <field name="key2">client_action_multi</field> + </record> + + <record id="actions_server_purchase_order_read" model="ir.actions.server"> + <field name="name">Mark read</field> + <field name="condition">True</field> + <field name="type">ir.actions.server</field> + <field name="model_id" ref="model_purchase_order"/> + <field name="state">code</field> + <field name="code">self.message_check_and_set_read(cr, uid, context.get('active_ids'), context=context)</field> + </record> + <record id="action_purchase_order_read" model="ir.values"> + <field name="name">action_purchase_order_read</field> + <field name="action_id" ref="actions_server_purchase_order_read"/> + <field name="value" eval="'ir.actions.server,' + str(ref('actions_server_purchase_order_read'))" /> + <field name="key">action</field> + <field name="model_id" ref="model_purchase_order" /> + <field name="model">purchase.order</field> + <field name="key2">client_action_multi</field> + </record> + <record model="ir.ui.view" id="purchase_order_calendar"> <field name="name">purchase.order.calendar</field> <field name="model">purchase.order</field> @@ -133,7 +170,7 @@ <field name="arch" type="xml"> <form string="Purchase Order" version="7.0"> <header> - <button name="purchase_confirm" states="draft,sent" string="Confirm Order" class="oe_highlight"/> + <button name="purchase_confirm" states="draft,sent" string="Confirm Quotation" class="oe_highlight"/> <button name="wkf_send_rfq" states="draft" string="Send RFQ" type="object" context="{'send_rfq':True}"/> <button name="wkf_send_rfq" states="confirmed" string="Resend Purchase Order" type="object" class="oe_highlight"/> <button name="action_cancel" states="except_picking,except_invoice,wait" string="Cancel" type="object" /> @@ -148,11 +185,13 @@ <field name="state" widget="statusbar" statusbar_visible="draft,approved,done" statusbar_colors='{"except_picking":"red","except_invoice":"red","confirmed":"blue","wait":"blue"}' readonly="1"/> </header> <sheet> - <h1> - <label string="Draft Purchase Order " attrs="{'invisible': [('state','not in',('draft','sent'))]}"/> - <label string="Purchase Order " attrs="{'invisible': [('state','in',('draft','sent'))]}"/> - <field name="name" class="oe_inline" readonly="1"/> - </h1> + <div class="oe_title"> + <h1> + <label string="Quotation " attrs="{'invisible': [('state','not in',('draft','sent'))]}"/> + <label string="Purchase Order " attrs="{'invisible': [('state','in',('draft','sent'))]}"/> + <field name="name" class="oe_inline" readonly="1"/> + </h1> + </div> <group> <group> <field name="partner_id" on_change="onchange_partner_id(partner_id)" context="{'search_default_supplier':1,'default_supplier':1,'default_customer':0}"/> @@ -175,15 +214,17 @@ <field name="product_qty"/> <field name="product_uom" groups="product.group_uom"/> <field name="price_unit"/> - <field name="price_subtotal"/> + <field name="price_subtotal"/> </tree> </field> - <group class="oe_subtotal_footer"> + <group class="oe_subtotal_footer oe_right"> <field name="amount_untaxed"/> <field name="amount_tax"/> - <field name="amount_total" class="oe_subtotal_footer_separator"/> - <button name="button_dummy" states="draft" string="Compute" type="object" icon="gtk-execute"/> - </group> + <div class="oe_subtotal_footer_separator oe_inline" colspan="2"> + <button name="button_dummy" states="draft" string="Compute" type="object" icon="gtk-execute" class="oe_inline oe_left"/> + <field name="amount_total"/> + </div> + </group> <div class="oe_clear"/> <label for="notes"/> <field name="notes"/> @@ -191,23 +232,27 @@ <page string="Delivery & Invoicing"> <group> <group string="Delivery"> - <field name="dest_address_id" on_change="onchange_dest_address_id(dest_address_id)"/> + <field name="dest_address_id" string="Customer Address" on_change="onchange_dest_address_id(dest_address_id)"/> <field name="minimum_planned_date"/> <field name="location_id" groups="stock.group_locations"/> - <field name="shipped"/> + <field name="shipped" groups="base.group_no_one"/> + </group> + <group> + <field name="validator" groups="base.group_no_one"/> + <field name="date_approve"/> </group> - <group string="Invoice Control"> + </group> + <group string="Invoicing"> + <group> <field name="invoice_method"/> - <field name="fiscal_position" widget="selection"/> - <field name="invoiced" groups="base.group_no_one"/> + <field name="invoiced"/> </group> - <group groups="base.group_no_one" string="Purchase Control"> - <field name="validator"/> - <field name="date_approve"/> + <group> + <field name="fiscal_position"/> </group> - <separator string="Invoices"/> - <field name="invoice_ids" context="{'type':'in_invoice', 'journal_type':'purchase'}"/> </group> + <separator string="Invoices"/> + <field name="invoice_ids" context="{'type':'in_invoice', 'journal_type':'purchase'}"/> </page> </notebook> </sheet> @@ -259,6 +304,11 @@ <group> <field name="name" string="Reference"/> <separator orientation="vertical"/> + <filter icon="terp-mail-message-new" + string="Inbox" help="Unread messages" + name="needaction_pending" + domain="[('needaction_pending','=',True)]"/> + <separator orientation="vertical"/> <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"/> <filter icon="terp-gtk-go-back-rtl" name="not_invoiced" string="Not Invoiced" domain="[('invoice_ids','=', False)]" help="Purchase orders that include lines not invoiced."/> @@ -336,7 +386,6 @@ </record> <menuitem action="purchase_form_action" id="menu_purchase_form_action" parent="menu_procurement_management" sequence="6"/> - <record id="purchase_order_line_form" model="ir.ui.view"> <field name="name">purchase.order.line.form</field> <field name="model">purchase.order.line</field> @@ -344,9 +393,6 @@ <field name="arch" type="xml"> <form string="Purchase Order Line" version="7.0"> <sheet> - <label for="name" class="oe_edit_only"/> - <h1><field name="name"/></h1> - <label for="product_id" class="oe_edit_only"/> <group> <group> <field name="product_id" on_change="onchange_product_id(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,price_unit,notes,context)"/> @@ -356,23 +402,23 @@ <field name="product_uom" groups="product.group_uom" on_change="onchange_product_uom(parent.pricelist_id,product_id,product_qty,product_uom,parent.partner_id, parent.date_order,parent.fiscal_position,date_planned,name,price_unit,notes,context)" class="oe_inline"/> </div> <field name="price_unit"/> + <field name="taxes_id" widget="many2many_tags" domain="[('parent_id','=',False),('type_tax_use','!=','sale')]"/> </group> <group> + <field name="name"/> <field name="date_planned" widget="date"/> + <field name="account_analytic_id" colspan="2" groups="purchase.group_analytic_accounting" domain="[('parent_id','!=',False)]" /> <field name="company_id" groups="base.group_multi_company" widget="selection"/> - <field name="account_analytic_id" colspan="4" groups="purchase.group_analytic_accounting" domain="[('parent_id','!=',False)]" /> - <field name="taxes_id" widget="many2many_tags" - domain="[('parent_id','=',False),('type_tax_use','!=','sale')]"/> </group> <group string="Notes" colspan="4"> - <field name="notes"/> + <field name="notes" nolabel="1" placeholder="Add a note on the purchase line..."/> </group> <group string="Invoices" colspan="4"> - <field name="invoiced"/> - <field name="invoice_lines" nolabel="1" colspan="2"/> + <field name="invoice_lines" nolabel="1"/> + </group> + <group string="Stock Moves" colspan="4"> + <field name="move_ids" nolabel="1"/> </group> - <separator string="Stock Moves"/> - <field name="move_ids"/> </group> </sheet> </form> @@ -410,11 +456,11 @@ <label for="order_id" class="oe_edit_only"/> <h1> <field name="order_id" class="oe_inline"/> + <label string="," attrs="{'invisible':[('date_order','=',False)]}"/> <field name="date_order" class="oe_inline"/> </h1> <label for="partner_id" class="oe_edit_only"/> <h2><field name="partner_id"/></h2> - <group> <group> <field name="name"/> diff --git a/addons/purchase/stock_view.xml b/addons/purchase/stock_view.xml index df6ba9531f3..f9d6239e0a5 100644 --- a/addons/purchase/stock_view.xml +++ b/addons/purchase/stock_view.xml @@ -24,27 +24,6 @@ </field> </record> - <act_window - domain="[('purchase_id', '=', active_id)]" - id="act_purchase_order_2_stock_picking" - name="Incoming Shipments" - res_model="stock.picking.in" - src_model="purchase.order" - context="{'default_purchase_id': active_id, 'contact_display': 'partner', 'default_type': 'in'}" /> - - <record id="action_picking_in_tree_view" model="ir.actions.act_window.view"> - <field eval="1" name="sequence"/> - <field name="view_mode">tree</field> - <field name="view_id" ref="stock.view_picking_in_tree"/> - <field name="act_window_id" ref="act_purchase_order_2_stock_picking"/> - </record> - <record id="action_picking_in_form_view" model="ir.actions.act_window.view"> - <field eval="2" name="sequence"/> - <field name="view_mode">form</field> - <field name="view_id" ref="stock.view_picking_in_form"/> - <field name="act_window_id" ref="act_purchase_order_2_stock_picking"/> - </record> - <record id="stock_picking_in_inherit_purchase" model="ir.ui.view"> <field name="name">Incoming Picking Inherited</field> <field name="model">stock.picking.in</field> @@ -78,6 +57,7 @@ <separator orientation="vertical"/> <field name="stock_journal_id" widget="selection"/> <field name="company_id" widget="selection" groups="base.group_multi_company"/> + <field name="purchase_id" /> </group> <newline/> <group expand="0" string="Group By..." colspan="4" col="8"> @@ -96,7 +76,33 @@ </search> </field> </record> - + <record id="act_purchase_order_2_stock_picking" model="ir.actions.act_window"> + <field name="name">Incoming Shipments</field> + <field name="res_model">stock.picking.in</field> + <field name="type">ir.actions.act_window</field> + <field name="view_type">form</field> + <field name="view_mode">tree,form,calendar</field> + <field name="domain">[('type','=','in')]</field> + <field name="context">{'search_default_purchase_id': active_id,'default_type': 'in'}</field> + <field name="search_view_id" ref="view_picking_in_search_picking_to_invoice"/> + </record> + + <record id="purchase_order_2_stock_picking" model="ir.ui.view"> + <field name="name">Purchase Picking Inherited</field> + <field name="model">purchase.order</field> + <field name="type">form</field> + <field name="inherit_id" ref="purchase.purchase_order_form"/> + <field name="arch" type="xml"> + <xpath expr="//div[contains(@class, 'oe_title')]" position="before"> + <div class="oe_right oe_button_box" name="buttons"> + <button type="action" + name="%(act_purchase_order_2_stock_picking)d" + string="Incoming Shipments" states="approved"/> + </div> + </xpath> + </field> + </record> + <record id="action_picking_tree4_picking_to_invoice" model="ir.actions.act_window"> <field name="name">Based on Incoming Shipments</field> <field name="res_model">stock.picking</field> diff --git a/addons/purchase_requisition/purchase_requisition.py b/addons/purchase_requisition/purchase_requisition.py index ec7bc005d04..1b0c36b114b 100644 --- a/addons/purchase_requisition/purchase_requisition.py +++ b/addons/purchase_requisition/purchase_requisition.py @@ -183,13 +183,6 @@ class purchase_requisition(osv.osv): return res - def get_needaction_user_ids(self, cr, uid, ids, context=None): - result = dict.fromkeys(ids, []) - for obj in self.browse(cr, uid, ids, context=context): - if (obj.state == 'draft') and obj.user_id: - result[obj.id] = [obj.user_id.id] - return result - 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) diff --git a/addons/purchase_requisition/purchase_requisition_view.xml b/addons/purchase_requisition/purchase_requisition_view.xml index f6b63ebc77b..2cfde675b77 100644 --- a/addons/purchase_requisition/purchase_requisition_view.xml +++ b/addons/purchase_requisition/purchase_requisition_view.xml @@ -8,7 +8,7 @@ <field name="model">purchase.order</field> <field name="inherit_id" ref="purchase.purchase_order_form"/> <field name="arch" type="xml"> - <field name="shipped" position="after"> + <field name="validator" position="before"> <field name="requisition_id"/> </field> </field> @@ -41,12 +41,13 @@ </header> <sheet> <div class="oe_edit_only"> - <label for="name" class="oe_inline"/>, + <label for="name" class="oe_inline"/> <label for="origin" class="oe_inline"/> </div> <h1> - <field name="name" class="oe_inline"/>, - <field name="origin" class="oe_inline"/> + <field name="name" class="oe_inline"/> + <label string="," attrs="{'invisible':[('origin','=',False)]}"/> + <field name="origin" class="oe_inline" placeholder="PO0025"/> </h1> <group> <group> diff --git a/addons/sale/res_partner_view.xml b/addons/sale/res_partner_view.xml index 6a011749282..3a5799b2a65 100644 --- a/addons/sale/res_partner_view.xml +++ b/addons/sale/res_partner_view.xml @@ -13,7 +13,7 @@ </field> <xpath expr="//h4[@class='oe_partner_heading']" position="after"> <a name="%(sale.act_res_partner_2_sale_order)d" type="action"> - <t t-if="record.sale_order_count.value">Sales(<t t-esc="record.sale_order_count.value" />)</t> + <t t-if="record.sale_order_count.value"><t t-esc="record.sale_order_count.value" /> Sales</t> </a> </xpath> </field> diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 353228df599..0a3ebbe72ac 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -1022,10 +1022,10 @@ class sale_order(osv.osv): # ------------------------------------------------ def get_needaction_user_ids(self, cr, uid, ids, context=None): - result = dict.fromkeys(ids, []) + 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] = [obj.user_id.id] + result[obj.id].append(obj.user_id.id) return result def create_send_note(self, cr, uid, ids, context=None): diff --git a/addons/sale/sale_view.xml b/addons/sale/sale_view.xml index 239cffe481f..46cf4905ff7 100644 --- a/addons/sale/sale_view.xml +++ b/addons/sale/sale_view.xml @@ -12,15 +12,16 @@ <form string="Sales Shop" version="7.0"> <label for="name" class="oe_edit_only"/> <h1><field name="name"/></h1> + <label for="warehouse_id" class="oe_edit_only"/> + <h2><field name="warehouse_id" required="1"/></h2> <group> <group> - <field name="warehouse_id" required="1" widget="selection"/> - <field name="company_id" widget="selection" groups="base.group_multi_company"/> - </group> - <group> <field name="payment_default_id"/> <field domain="[('type','=','sale')]" name="pricelist_id" groups="product.group_sale_pricelist"/> + </group> + <group> <field name="project_id" groups="analytic.group_analytic_accounting"/> + <field name="company_id" widget="selection" groups="base.group_multi_company"/> </group> </group> </form> @@ -53,6 +54,43 @@ <menuitem action="action_shop_form" id="menu_action_shop_form" parent="base.menu_base_config" sequence="35" groups="stock.group_locations"/> + <!-- Sale order Read/Unread actions --> + <record id="actions_server_sale_order_unread" model="ir.actions.server"> + <field name="name">Mark unread</field> + <field name="condition">True</field> + <field name="type">ir.actions.server</field> + <field name="model_id" ref="model_sale_order"/> + <field name="state">code</field> + <field name="code">self.message_check_and_set_unread(cr, uid, context.get('active_ids'), context=context)</field> + </record> + <record id="action_sale_order_unread" model="ir.values"> + <field name="name">action_sale_order_unread</field> + <field name="action_id" ref="actions_server_sale_order_unread"/> + <field name="value" eval="'ir.actions.server,' + str(ref('actions_server_sale_order_unread'))" /> + <field name="key">action</field> + <field name="model_id" ref="model_sale_order" /> + <field name="model">sale.order</field> + <field name="key2">client_action_multi</field> + </record> + + <record id="actions_server_sale_order_read" model="ir.actions.server"> + <field name="name">Mark read</field> + <field name="condition">True</field> + <field name="type">ir.actions.server</field> + <field name="model_id" ref="model_sale_order"/> + <field name="state">code</field> + <field name="code">self.message_check_and_set_read(cr, uid, context.get('active_ids'), context=context)</field> + </record> + <record id="action_sale_order_read" model="ir.values"> + <field name="name">action_sale_order_read</field> + <field name="action_id" ref="actions_server_sale_order_read"/> + <field name="value" eval="'ir.actions.server,' + str(ref('actions_server_sale_order_read'))" /> + <field name="key">action</field> + <field name="model_id" ref="model_sale_order" /> + <field name="model">sale.order</field> + <field name="key2">client_action_multi</field> + </record> + <record id="view_sale_order_calendar" model="ir.ui.view"> <field name="name">sale.order.calendar</field> <field name="model">sale.order</field> @@ -297,6 +335,11 @@ <search string="Search Sales Order"> <field name="name" string="Sales Order" filter_domain="['|',('name','ilike',self),('client_order_ref','ilike',self)]"/> <separator orientation="vertical"/> + <filter icon="terp-mail-message-new" + string="Inbox" help="Unread messages" + name="needaction_pending" + domain="[('needaction_pending','=',True)]"/> + <separator orientation="vertical"/> <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'))]"/> <filter icon="terp-dolar_ok!" string="To Invoice" domain="[('state','=','manual')]" help="Sales Order ready to be invoiced"/> diff --git a/addons/share/static/src/css/share.css b/addons/share/static/src/css/share.css index 997c070fe54..dbbb8bea943 100644 --- a/addons/share/static/src/css/share.css +++ b/addons/share/static/src/css/share.css @@ -1,7 +1,4 @@ -button.oe_share_mail { - display: none; -} - -a.oe_share_link, a.oe_share { +/* Added invite button, hidden by default */ +button.oe_share_invite { display: none; } diff --git a/addons/share/static/src/js/share.js b/addons/share/static/src/js/share.js index e88616546a3..33aa3d1aab9 100644 --- a/addons/share/static/src/js/share.js +++ b/addons/share/static/src/js/share.js @@ -50,29 +50,46 @@ openerp.share = function(session) { session.connection.share_flag.done(yes).fail(no); } + /* Extend the Sidebar to add Share and Embed links in the 'More' menu */ session.web.Sidebar = session.web.Sidebar.extend({ - add_default_sections: function() { - this._super(); + + start: function() { var self = this; + this._super(this); has_share(function() { - self.add_items('other', [{ - label: 'Share', - callback: self.on_sidebar_click_share, - classname: 'oe_share', - }]); + self.add_items('other', [ + { label: 'Share', + callback: self.on_click_share, + classname: 'oe_share' }, + { label: 'Embed', + callback: self.on_click_share_link, + classname: 'oe_share' }, + ]); }); }, - on_sidebar_click_share: function(item) { + + on_click_share: function(item) { + var view = this.getParent() + launch_wizard(this, view, 'emails', false); + }, + + on_click_share_link: function(item) { var view = this.getParent() - launch_wizard(this, view); + launch_wizard(this, view, 'embedded', false); }, }); + /** + * Extends mail (Chatter widget) + * - show the 'invite' button' only we came on the form view through + * an action. We do this because 'invite' is based on the share + * mechanism, and it tries to share an action. + */ session.mail.RecordThread.include( { start: function() { start_res = this._super.apply(this, arguments); if (has_action_id) { - this.$element.find('button.oe_share_mail').show(); + this.$element.find('button.oe_share_invite').show(); } return start_res; } @@ -83,12 +100,7 @@ openerp.share = function(session) { var self = this; this.check_if_action_is_defined(); has_share(function() { - self.$element.find('a.oe_share_link').click(self.on_click_share_link); - self.$element.find('a.oe_share').click(self.on_click_share); - self.$element.delegate('button.oe_share_mail', 'click', self.on_click_share_mail); - }, function() { - self.$element.find('a.oe_share_link').remove(); - self.$element.find('a.oe_share').remove(); + self.$element.delegate('button.oe_share_invite', 'click', self.on_click_share_invite); }); return this._super.apply(this, arguments); }, @@ -96,23 +108,13 @@ openerp.share = function(session) { check_if_action_is_defined: function() { if (this.action && this.action.id) { has_action_id = true; - this.$element.find('a.oe_share_link').show(); - this.$element.find('a.oe_share').show(); } else { has_action_id = false; } }, - - on_click_share_link: function(e) { - e.preventDefault(); - launch_wizard(this, this.views[this.active_view].controller, 'embedded', false); - }, - on_click_share: function(e) { - e.preventDefault(); - launch_wizard(this, this.views[this.active_view].controller, 'emails', false); - }, - on_click_share_mail: function(e) { + + on_click_share_invite: function(e) { e.preventDefault(); launch_wizard(this, this.views[this.active_view].controller, 'emails', true); }, diff --git a/addons/share/static/src/xml/share.xml b/addons/share/static/src/xml/share.xml index e51dc135c0b..1d9bc987afc 100644 --- a/addons/share/static/src/xml/share.xml +++ b/addons/share/static/src/xml/share.xml @@ -3,18 +3,11 @@ --> <templates id="template" xml:space="preserve"> - <t t-extend="ViewManagerAction"> - <t t-jquery=".oe_shortcuts_toggle" t-operation="after"> - <a class="oe_share_link" href="#share_link" title="Link or embed..."><img t-att-src='_s + "/share/static/src/img/share.png"'/></a> - <a class="oe_share" href="#share" title="Share with..."><img t-att-src='_s + "/share/static/src/img/share.png"'/></a> - </t> - </t> - - <t t-extend="RecordThread"> + <!-- Extends Chatter widget in form view to add the invite button --> + <t t-extend="mail.RecordThread"> <t t-jquery="button.oe_mail_button_unfollow" t-operation="after"> - <button type="button" class="oe_share_mail oe_mail_button_mouseout">Invite</button> + <button type="button" class="oe_share_invite">Invite</button> </t> </t> - </templates> diff --git a/addons/share/wizard/share_wizard.py b/addons/share/wizard/share_wizard.py index 55d43453775..41871480f45 100644 --- a/addons/share/wizard/share_wizard.py +++ b/addons/share/wizard/share_wizard.py @@ -48,7 +48,7 @@ RANDOM_PASS_CHARACTERS = 'aaaabcdeeeefghjkmnpqrstuvwxyzAAAABCDEEEEFGHJKLMNPQRSTU def generate_random_pass(): return ''.join(random.sample(RANDOM_PASS_CHARACTERS,10)) -class share_wizard(osv.osv_memory): +class share_wizard(osv.TransientModel): _name = 'share.wizard' _description = 'Share Wizard' @@ -177,9 +177,10 @@ class share_wizard(osv.osv_memory): 'name': fields.char('Share Title', size=64, required=True, help="Title for the share (displayed to users as menu and shortcut name)"), 'record_name': fields.char('Record name', size=128, help="Name of the shared record, if sharing a precise record"), 'message': fields.text("Personal Message", help="An optional personal message, to be included in the email notification."), - - 'embed_code': fields.function(_embed_code, type='text'), - 'embed_option_title': fields.boolean("Display title"), + 'embed_code': fields.function(_embed_code, type='text', string='Code', + help="Embed this code in your documents to provide a link to the "\ + "shared document."), + 'embed_option_title': fields.boolean('Display title'), 'embed_option_search': fields.boolean('Display search view'), 'embed_url': fields.function(_embed_url, string='Share URL', type='char', size=512, readonly=True), } @@ -903,8 +904,11 @@ class share_result_line(osv.osv_memory): def _share_url(self, cr, uid, ids, _fieldname, _args, context=None): result = dict.fromkeys(ids, '') for this in self.browse(cr, uid, ids, context=context): - data = dict(dbname=cr.dbname, login=this.login, password='') - result[this.id] = this.share_wizard_id.share_url_template() % data + data = dict(dbname=cr.dbname, login=this.login, password=this.password) + if this.share_wizard_id and this.share_wizard_id.action_id: + data['action_id'] = this.share_wizard_id.action_id.id + ctx = dict(context, share_url_template_hash_arguments=['action_id']) + result[this.id] = this.share_wizard_id.share_url_template(context=ctx) % data return result _columns = { diff --git a/addons/share/wizard/share_wizard_view.xml b/addons/share/wizard/share_wizard_view.xml index ee5d4dd23b9..58734caa25b 100644 --- a/addons/share/wizard/share_wizard_view.xml +++ b/addons/share/wizard/share_wizard_view.xml @@ -39,17 +39,20 @@ <group> <field name="user_type" invisible="1"/> <field name="invite" invisible="1"/> - </group> + </group> <group> - <group name="emails_group" attrs="{'invisible':['|', ('user_type', '!=', 'emails'), ('invite', '=', True)]}" string="Share with these People (one email per line)"> - <field colspan="2" nolabel="1" name="new_users" attrs="{'required':[('user_type','=','emails'), ('invite', '!=', True)]}"/> + <group colspan="4" name="emails_group" attrs="{'invisible':['|', ('user_type', '!=', 'emails'), ('invite', '=', True)]}" + string="Share with these People (one email per line)"> + <field nolabel="1" name="new_users" attrs="{'required':[('user_type','=','emails'), ('invite', '!=', True)]}"/> </group> - <group name="email_lines" attrs="{'invisible':[('invite', '!=', True)]}" string="Share with these People (one email per line)"> + <group colspan="4" name="email_lines" attrs="{'invisible':['|', ('invite', '!=', True), ('user_type', '!=', 'emails')]}" + string="Share with these People (one email per line)"> <field name="email_1"/> <field name="email_2"/> <field name="email_3"/> </group> - <group attrs="{'invisible':[('user_type', '=', 'embedded')]}" string="Include an Optional Personal Message"> + <group colspan="4" attrs="{'invisible':[('user_type', '=', 'embedded')]}" + string="Include an Optional Personal Message"> <field name="message" colspan="2" nolabel="1"/> </group> <group attrs="{'invisible':[('invite', '=', True)]}" string="Sharing Options"> @@ -71,8 +74,8 @@ <field name="user_type" invisible="1"/> <field name="access_mode" invisible="1"/> </header> - <group attrs="{'invisible':[('user_type','=','embedded')]}"> - <separator string="An email notification with instructions has been sent to the following people:"/> + <group attrs="{'invisible':[('user_type','=','embedded')]}" + string="An email notification with instructions has been sent to the following people:"> <field name="result_line_ids" nolabel="1" mode="tree"> <tree string="Summary"> <field name="login"/> @@ -85,14 +88,17 @@ </field> </group> <group colspan="4" col="1" attrs="{'invisible':[('user_type','!=','embedded')]}"> - <separator string="Use this Link"/> - <field name="embed_url"/> - <separator string="Or insert the following code where you want to embed your documents"/> - <group string="Options"> - <field name="embed_option_title" on_change="onchange_embed_options(embed_option_title, embed_option_search)"/> - <field name="embed_option_search" on_change="onchange_embed_options(embed_option_title, embed_option_search)" attrs="{'invisible':[('access_mode','=','readonly')]}"/> + <group string="Use this link"> + <field name="embed_url"/> + </group> + <group string="Or insert the following code where you want to embed your documents"> <field name="embed_code"/> </group> + <group string="Embedded code options"> + <field name="embed_option_title" on_change="onchange_embed_options(embed_option_title, embed_option_search)"/> + <field name="embed_option_search" on_change="onchange_embed_options(embed_option_title, embed_option_search)" + attrs="{'invisible':[('access_mode','=','readonly')]}"/> + </group> </group> </form> diff --git a/addons/stock/product_view.xml b/addons/stock/product_view.xml index e28eb0644d0..04a0959739a 100644 --- a/addons/stock/product_view.xml +++ b/addons/stock/product_view.xml @@ -21,14 +21,14 @@ <field name="type">form</field> <field name="inherit_id" ref="product.product_category_form_view"/> <field name="arch" type="xml"> - <form position="inside" version="7.0"> - <group string="Accounting Stock Properties"> + <data> + <xpath expr="/form/sheet//group[@name='account_stock_property']" position="inside"> <field name="property_stock_account_input_categ" domain="[('type','<>','view'),('type','<>','consolidation')]"/> <field name="property_stock_account_output_categ" domain="[('type','<>','view'),('type','<>','consolidation')]"/> <field name="property_stock_valuation_account_id" domain="[('type','<>','view'), ('type','<>','consolidation')]"/> <field name="property_stock_journal"/> - </group> - </form> + </xpath> + </data> </field> </record> diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml index 3107f0c9f14..b9a55841c0a 100644 --- a/addons/stock/stock_view.xml +++ b/addons/stock/stock_view.xml @@ -790,7 +790,7 @@ <button name="force_assign" states="confirmed" string="Force Availability" type="object" icon="gtk-jump-to"/> <button name="action_confirm" states="draft" string="Confirm" type="object" icon="gtk-apply"/> <button name="cancel_assign" states="assigned" string="Cancel Availability" type="object" icon="gtk-find"/> - <field name="state" widget="statusbar" status_visible="draft,assigned,done" /> + <field name="state" widget="statusbar" statusbar_visible="draft,assigned,done" /> </header> <group> <group string="Move Information"> @@ -1419,50 +1419,47 @@ <label for="product_qty"/> <div> <field name="product_qty" on_change="onchange_quantity(product_id, product_qty, product_uom, product_uos)" class="oe_inline"/> - <field name="product_uom" string="Unit Of Measure" groups="product.group_uom" class="oe_inline"/> + <field name="product_uom" string="Unit Of Measure" groups="product.group_uom" class="oe_inline"/> <button name="%(stock.move_scrap)d" string="Scrap" type="action" icon="gtk-convert" context="{'scrap': True}" - states="draft,waiting,confirmed,assigned"/> + states="draft,waiting,confirmed,assigned" colspan="1"/> </div> <label for="product_uos_qty"/> <div> <field name="product_uos_qty" groups="product.group_uos" class="oe_inline" on_change="onchange_uos_quantity(product_id, product_uos_qty, product_uos, product_uom)"/> - <field name="product_uos" groups="product.group_uos"/> + <field name="product_uos" groups="product.group_uos" class="oe_inline"/> </div> <field name="name" string="Reason"/> - </group> - <group> - <field name="picking_id"/> - <field name="company_id" groups="base.group_multi_company" widget="selection"/> + <field name="company_id" groups="base.group_multi_company"/> <field name="location_id" groups="stock.group_locations"/> <field name="location_dest_id" groups="stock.group_locations"/> <field name="partner_id" groups="stock.group_locations"/> - <field name="create_date" groups="base.group_no_one" /> - <field name="date" groups="base.group_no_one" /> - <field name="date_expected" on_change="onchange_date(date,date_expected)"/> </group> - <group> - <label name="tracking_id"/> + <field name="picking_id"/> + <field name="create_date" groups="base.group_no_one" /> + <field name="date" groups="base.group_no_one" string="Move Date"/> + <field name="date_expected" on_change="onchange_date(date,date_expected)"/> + <label for="tracking_id"/> <div> <field name="tracking_id" groups="stock.group_tracking_lot" class="oe_inline"/> <button name="%(split_into)d" string="New Pack" type="action" groups="product.group_stock_packaging" icon="terp-stock_effects-object-colorize" - states="draft,assigned,confirmed,done" colspan="1"/> + states="draft,assigned,confirmed,done" class="oe_inline"/> </div> - <label name="prodlot_id"/> + <label for="prodlot_id"/> <div> <field name="prodlot_id" groups="stock.group_production_lot" context="{'location_id':location_id, 'product_id':product_id}" domain="[('product_id','=?',product_id)]" class="oe_inline" on_change="onchange_lot_id(prodlot_id,product_qty, location_id, product_id, product_uom)"/> <button name="%(track_line)d" - groups="stock.group_tracking_lot" class="oe_inline" - states="draft,waiting,confirmed,assigned,done" - string="Split" type="action" icon="terp-stock_effects-object-colorize"/> + groups="stock.group_tracking_lot" class="oe_inline" + states="draft,waiting,confirmed,assigned,done" + string="Split" type="action" icon="terp-stock_effects-object-colorize" /> </div> </group> </group> @@ -1613,13 +1610,6 @@ src_model="product.product"/> <act_window - domain="[('move_lines','=',active_id)]" - id="act_relate_picking" - name="Related Picking" - res_model="stock.picking" - src_model="stock.move"/> - - <act_window context="{'search_default_future': 1,'search_default_product_id': [active_id], 'default_product_id': active_id}" domain="[('state','in',('waiting','confirmed','assigned'))]" id="act_product_stock_move_futur_open" diff --git a/addons/wiki/wiki_view.xml b/addons/wiki/wiki_view.xml index 840692e1f01..700585122d3 100644 --- a/addons/wiki/wiki_view.xml +++ b/addons/wiki/wiki_view.xml @@ -129,16 +129,22 @@ <field name="arch" type="xml"> <form string="Wiki" version="7.0"> <sheet> - <group col="6" colspan="4"> - <field name="name" colspan="6"/> - <field name="group_id" string="Topic" on_change="onchange_group_id(group_id, text_area)"/> - <field name="parent_id" domain="[('group_id','=',group_id)]"/> - <field name="section"/> + <div class="oe_title"> + <label for="name" class="oe_edit_only"/> + <h1><field name="name" select="1" /></h1> + <label for="group_id" class="oe_edit_only"/> + <h2> + <field name="group_id" string="Topic" select="1" on_change="onchange_group_id(group_id, text_area)"/></h2> + </div> + <group> + <group> + <field name="parent_id" domain="[('group_id','=',group_id)]"/> + <field name="section"/> + </group> </group> <notebook colspan="4"> <page string="Content"> - <separator colspan="4" string="Page Content"/> - <field name="text_area" nolabel="1" colspan="4" widget="text_wiki"/> + <field name="text_area" nolabel="1" colspan="4" widget="text_wiki" placeholder="Wiki Content"/> </page> </notebook> <group col="2" colspan="2" groups="base.group_no_one"> |
