summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThibault Delavallée <tde@openerp.com>2013-03-15 12:43:38 +0100
committerThibault Delavallée <tde@openerp.com>2013-03-15 12:43:38 +0100
commit4484a3197ca558341ce595df41ffdc072a8d05d2 (patch)
tree160895b3cd052ef5a0661ed2ee02a53494dab207
parent4387187b8bdac6223979e9a652b6ef6a19096804 (diff)
parentd0f5b28bd0da703e3d5a23cb68db91a6a87482dc (diff)
[MERGE] Sync with trunk, resolved conflicts due to forward port of 7.0
bzr revid: tde@openerp.com-20130315114338-54scb00rkp7zwr30
-rw-r--r--addons/account/account.py30
-rw-r--r--addons/account/account_invoice.py24
-rw-r--r--addons/account/account_invoice_view.xml17
-rw-r--r--addons/account/account_move_line.py2
-rw-r--r--addons/account/account_view.xml18
-rw-r--r--addons/account/partner_view.xml1
-rw-r--r--addons/account/project/project_view.xml3
-rw-r--r--addons/account/report/account_financial_report.py4
-rw-r--r--addons/account_analytic_analysis/account_analytic_analysis.py11
-rw-r--r--addons/account_analytic_analysis/account_analytic_analysis_cron.xml13
-rw-r--r--addons/account_analytic_analysis/account_analytic_analysis_view.xml10
-rw-r--r--addons/account_analytic_plans/account_analytic_plans.py4
-rw-r--r--addons/account_asset/account_asset.py11
-rw-r--r--addons/account_budget/account_budget_view.xml2
-rw-r--r--addons/account_cancel/account_cancel_view.xml4
-rw-r--r--addons/account_followup/account_followup.py3
-rw-r--r--addons/account_followup/account_followup_customers.xml13
-rw-r--r--addons/account_followup/tests/test_account_followup.py37
-rw-r--r--addons/account_payment/account_payment_view.xml2
-rw-r--r--addons/account_voucher/account_voucher_view.xml4
-rw-r--r--addons/account_voucher/invoice.py2
-rw-r--r--addons/account_voucher/voucher_payment_receipt_view.xml4
-rw-r--r--addons/account_voucher/voucher_sales_purchase_view.xml14
-rw-r--r--addons/analytic_contract_hr_expense/analytic_contract_hr_expense_view.xml10
-rw-r--r--addons/auth_signup/__openerp__.py1
-rw-r--r--addons/auth_signup/controllers/main.py14
-rw-r--r--addons/auth_signup/static/src/js/auth_signup.js155
-rw-r--r--addons/auth_signup/static/src/xml/auth_signup.xml28
-rw-r--r--addons/base_action_rule/base_action_rule.py7
-rw-r--r--addons/base_action_rule/base_action_rule_view.xml2
-rw-r--r--addons/base_calendar/base_calendar_view.xml2
-rw-r--r--addons/base_report_designer/base_report_designer.py8
-rw-r--r--addons/base_report_designer/plugin/openerp_report_designer.zipbin116306 -> 116477 bytes
-rw-r--r--addons/base_report_designer/plugin/openerp_report_designer/bin/script/ExportToRML.py8
-rw-r--r--addons/base_report_designer/plugin/openerp_report_designer/bin/script/Fields.py4
-rw-r--r--addons/base_report_designer/plugin/openerp_report_designer/bin/script/Repeatln.py50
-rw-r--r--addons/base_report_designer/plugin/openerp_report_designer/bin/script/SendToServer.py9
-rw-r--r--addons/base_report_designer/plugin/openerp_report_designer/bin/script/Translation.py60
-rw-r--r--addons/base_report_designer/plugin/openerp_report_designer/bin/script/lib/functions.py24
-rw-r--r--addons/base_report_designer/plugin/openerp_report_designer/bin/script/lib/tools.py18
-rw-r--r--addons/base_report_designer/plugin/openerp_report_designer/bin/script/modify.py46
-rw-r--r--addons/base_report_designer/plugin/openerp_report_designer/test/test_fields.py34
-rw-r--r--addons/board/static/src/js/dashboard.js6
-rw-r--r--addons/board/static/src/xml/board.xml2
-rw-r--r--addons/crm/crm_data.xml3
-rw-r--r--addons/crm/crm_lead.py30
-rw-r--r--addons/crm/crm_lead_view.xml18
-rw-r--r--addons/crm/crm_phonecall_view.xml2
-rw-r--r--addons/crm/wizard/crm_opportunity_to_phonecall.py2
-rw-r--r--addons/crm/wizard/crm_opportunity_to_phonecall_view.xml8
-rw-r--r--addons/crm_claim/crm_claim_view.xml2
-rw-r--r--addons/crm_helpdesk/crm_helpdesk_view.xml8
-rw-r--r--addons/crm_partner_assign/__init__.py2
-rw-r--r--addons/crm_partner_assign/__openerp__.py1
-rw-r--r--addons/crm_partner_assign/crm_partner_assign.py (renamed from addons/crm_partner_assign/partner_geo_assign.py)8
-rw-r--r--addons/crm_partner_assign/crm_partner_assign_data.xml44
-rw-r--r--addons/crm_partner_assign/wizard/crm_forward_to_partner.py109
-rw-r--r--addons/crm_partner_assign/wizard/crm_forward_to_partner_view.xml3
-rw-r--r--addons/document/content_index.py4
-rw-r--r--addons/document/std_index.py13
-rw-r--r--addons/email_template/email_template.py5
-rw-r--r--addons/email_template/email_template_view.xml4
-rw-r--r--addons/email_template/res_partner.py7
-rw-r--r--addons/email_template/res_partner_view.xml14
-rw-r--r--addons/email_template/wizard/mail_compose_message.py17
-rw-r--r--addons/event/event_view.xml4
-rw-r--r--addons/fetchmail/fetchmail.py13
-rw-r--r--addons/hr_evaluation/hr_evaluation_view.xml4
-rw-r--r--addons/hr_expense/__openerp__.py4
-rw-r--r--addons/hr_expense/hr_expense.py286
-rw-r--r--addons/hr_expense/hr_expense_view.xml4
-rw-r--r--addons/hr_expense/hr_expense_workflow.xml2
-rw-r--r--addons/hr_expense/report/hr_expense_report.py3
-rw-r--r--addons/hr_expense/report/hr_expense_report_view.xml1
-rw-r--r--addons/hr_expense/test/expense_process.yml5
-rw-r--r--addons/hr_payroll/hr_payroll_view.xml2
-rw-r--r--addons/hr_recruitment/hr_recruitment.py9
-rw-r--r--addons/hr_recruitment/hr_recruitment_view.xml4
-rw-r--r--addons/l10n_fr_hr_payroll/report/fiche_paye.rml4
-rw-r--r--addons/l10n_in_hr_payroll/l10n_in_hr_payroll_view.xml2
-rw-r--r--addons/lunch/lunch_demo.xml3
-rw-r--r--addons/lunch/lunch_view.xml2
-rw-r--r--addons/lunch/security/lunch_security.xml1
-rw-r--r--addons/mail/mail_followers.py38
-rw-r--r--addons/mail/mail_group_view.xml2
-rw-r--r--addons/mail/mail_mail.py27
-rw-r--r--addons/mail/mail_mail_view.xml11
-rw-r--r--addons/mail/mail_message.py11
-rw-r--r--addons/mail/mail_message_view.xml8
-rw-r--r--addons/mail/mail_thread.py308
-rw-r--r--addons/mail/mail_thread_view.xml21
-rw-r--r--addons/mail/res_partner.py26
-rw-r--r--addons/mail/res_partner_view.xml3
-rw-r--r--addons/mail/res_users.py28
-rw-r--r--addons/mail/static/src/css/mail.css50
-rw-r--r--addons/mail/static/src/js/mail.js292
-rw-r--r--addons/mail/static/src/js/mail_followers.js28
-rw-r--r--addons/mail/static/src/xml/mail.xml64
-rw-r--r--addons/mail/tests/test_mail_features.py13
-rw-r--r--addons/mail/tests/test_mail_message.py4
-rw-r--r--addons/mail/wizard/invite.py1
-rw-r--r--addons/mail/wizard/mail_compose_message.py19
-rw-r--r--addons/mail/wizard/mail_compose_message_view.xml5
-rw-r--r--addons/marketing_campaign/marketing_campaign_view.xml6
-rw-r--r--addons/mrp/mrp_view.xml4
-rw-r--r--addons/mrp_operations/mrp_operations_view.xml2
-rw-r--r--addons/point_of_sale/account_bank_statement.py2
-rw-r--r--addons/point_of_sale/point_of_sale.py13
-rw-r--r--addons/point_of_sale/static/src/js/db.js2
-rw-r--r--addons/point_of_sale/test/01_order_to_payment.yml17
-rw-r--r--addons/portal/portal_view.xml65
-rw-r--r--addons/portal_anonymous/static/src/js/portal_anonymous.js26
-rw-r--r--addons/portal_sale/portal_sale_view.xml10
-rw-r--r--addons/procurement/procurement.py7
-rw-r--r--addons/procurement/procurement_view.xml2
-rw-r--r--addons/product/product.py2
-rw-r--r--addons/product/product_view.xml2
-rw-r--r--addons/product/security/product_security.xml4
-rw-r--r--addons/project/project.py11
-rw-r--r--addons/project/project_view.xml37
-rw-r--r--addons/project_issue/project_issue.py20
-rw-r--r--addons/project_issue/project_issue_data.xml15
-rw-r--r--addons/project_issue/project_issue_view.xml28
-rw-r--r--addons/project_issue/security/ir.model.access.csv1
-rw-r--r--addons/project_long_term/project_long_term_view.xml2
-rw-r--r--addons/purchase/edi/purchase_order_action_data.xml2
-rw-r--r--addons/purchase/purchase.py6
-rw-r--r--addons/purchase/purchase_view.xml10
-rw-r--r--addons/purchase_requisition/purchase_requisition_view.xml2
-rw-r--r--addons/sale/sale.py3
-rw-r--r--addons/sale/sale_data.xml4
-rw-r--r--addons/sale/sale_view.xml10
-rw-r--r--addons/sale_stock/sale_stock_view.xml28
-rw-r--r--addons/share/wizard/share_wizard.py6
-rw-r--r--addons/stock/report/picking.rml1
-rw-r--r--addons/stock/stock.py2
-rw-r--r--addons/stock/stock_view.xml19
-rw-r--r--addons/survey/survey_view.xml4
138 files changed, 1722 insertions, 990 deletions
diff --git a/addons/account/account.py b/addons/account/account.py
index acf27c42392..8c7f058acc6 100644
--- a/addons/account/account.py
+++ b/addons/account/account.py
@@ -1150,6 +1150,29 @@ class account_move(osv.osv):
_description = "Account Entry"
_order = 'id desc'
+ def account_move_prepare(self, cr, uid, journal_id, date=False, ref='', company_id=False, context=None):
+ '''
+ Prepares and returns a dictionary of values, ready to be passed to create() based on the parameters received.
+ '''
+ if not date:
+ date = fields.date.today()
+ period_obj = self.pool.get('account.period')
+ if not company_id:
+ user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
+ company_id = user.company_id.id
+ if context is None:
+ context = {}
+ #put the company in context to find the good period
+ ctx = context.copy()
+ ctx.update({'company_id': company_id})
+ return {
+ 'journal_id': journal_id,
+ 'date': date,
+ 'period_id': period_obj.find(cr, uid, date, context=ctx)[0],
+ 'ref': ref,
+ 'company_id': company_id,
+ }
+
def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
"""
Returns a list of tupples containing id, name, as internally it is called {def name_get}
@@ -1850,6 +1873,13 @@ class account_tax(osv.osv):
return result in the context
Ex: result=round(price_unit*0.21,4)
"""
+ def copy_data(self, cr, uid, id, default=None, context=None):
+ if default is None:
+ default = {}
+ name = self.read(cr, uid, id, ['name'], context=context)['name']
+ default = default.copy()
+ default.update({'name': name + _(' (Copy)')})
+ return super(account_tax, self).copy_data(cr, uid, id, default=default, context=context)
def get_precision_tax():
def change_digit_tax(cr):
diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py
index dbe17d1c60a..d771bc59506 100644
--- a/addons/account/account_invoice.py
+++ b/addons/account/account_invoice.py
@@ -22,6 +22,7 @@
import time
from lxml import etree
import openerp.addons.decimal_precision as dp
+import openerp.exceptions
from openerp import pooler
from openerp.osv import fields, osv, orm
@@ -302,16 +303,7 @@ class account_invoice(osv.osv):
('number_uniq', 'unique(number, company_id, journal_id, type)', 'Invoice Number must be unique per Company!'),
]
- def _find_partner(self, inv):
- '''
- Find the partner for which the accounting entries will be created
- '''
- #if the chosen partner is not a company and has a parent company, use the parent for the journal entries
- #because you want to invoice 'Agrolait, accounting department' but the journal items are for 'Agrolait'
- part = inv.partner_id
- if part.parent_id and not part.is_company:
- part = part.parent_id
- return part
+
def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
@@ -981,7 +973,7 @@ class account_invoice(osv.osv):
date = inv.date_invoice or time.strftime('%Y-%m-%d')
- part = self._find_partner(inv)
+ part = self.pool.get("res.partner")._find_accounting_partner(inv.partner_id)
line = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, part.id, date, context=ctx)),iml)
@@ -1753,6 +1745,16 @@ class res_partner(osv.osv):
'invoice_ids': fields.one2many('account.invoice.line', 'partner_id', 'Invoices', readonly=True),
}
+ def _find_accounting_partner(self, part):
+ '''
+ Find the partner for which the accounting entries will be created
+ '''
+ #if the chosen partner is not a company and has a parent company, use the parent for the journal entries
+ #because you want to invoice 'Agrolait, accounting department' but the journal items are for 'Agrolait'
+ if part.parent_id and not part.is_company:
+ part = part.parent_id
+ return part
+
def copy(self, cr, uid, id, default=None, context=None):
default = default or {}
default.update({'invoice_ids' : []})
diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml
index a37474db0af..8c0854bd6c6 100644
--- a/addons/account/account_invoice_view.xml
+++ b/addons/account/account_invoice_view.xml
@@ -145,8 +145,7 @@
<header>
<button name="invoice_open" states="draft,proforma2" string="Validate" class="oe_highlight" groups="account.group_account_invoice"/>
<button name="%(action_account_invoice_refund)d" type='action' string='Ask Refund' states='open,paid' groups="account.group_account_invoice"/>
- <button name="invoice_cancel" states="draft,proforma2" string="Cancel" groups="account.group_account_invoice"/>
- <button name="invoice_cancel" states="sale,open" string="Cancel" groups="base.group_no_one"/>
+ <button name="invoice_cancel" states="draft,proforma2" string="Cancel Invoice" groups="account.group_account_invoice"/>
<button name="action_cancel_draft" states="cancel" string="Set to Draft" type="object" groups="account.group_account_invoice"/>
<button name='%(action_account_state_open)d' type='action' string='Re-Open' groups="account.group_account_invoice" attrs="{'invisible':['|', ('state','&lt;&gt;','paid'), ('reconciled', '=', True)]}" help="This button only appears when the state of the invoice is 'paid' (showing that it has been fully reconciled) and auto-computed boolean 'reconciled' is False (depicting that it's not the case anymore). In other words, the invoice has been dereconciled and it does not fit anymore the 'paid' state. You should press this button to re-open it and let it continue its normal process after having resolved the eventual exceptions it may have created."/>
<field name="state" widget="statusbar" statusbar_visible="draft,open,paid" statusbar_colors='{"proforma":"blue","proforma2":"blue"}'/>
@@ -300,7 +299,7 @@
<button name="invoice_open" states="proforma2" string="Validate" groups="base.group_user"/>
<button name="invoice_proforma2" states="draft" string="PRO-FORMA" groups="account.group_proforma_invoices"/>
<button name="%(action_account_invoice_refund)d" type='action' string='Refund Invoice' states='open,proforma2,paid' groups="base.group_user"/>
- <button name="invoice_cancel" states="draft,proforma2,open" string="Cancel" groups="base.group_no_one"/>
+ <button name="invoice_cancel" states="draft,proforma2,open" string="Cancel Invoice" groups="base.group_no_one"/>
<button name="action_cancel_draft" states="cancel" string="Reset to Draft" type="object" groups="base.group_user"/>
<button name='%(action_account_state_open)d' type='action' string='Re-Open' groups="account.group_account_invoice" attrs="{'invisible':['|', ('state','&lt;&gt;','paid'), ('reconciled', '=', True)]}" help="This button only appears when the state of the invoice is 'paid' (showing that it has been fully reconciled) and auto-computed boolean 'reconciled' is False (depicting that it's not the case anymore). In other words, the invoice has been dereconciled and it does not fit anymore the 'paid' state. You should press this button to re-open it and let it continue its normal process after having resolved the eventual exceptions it may have created."/>
<!--button name="%(account_invoices)d" string="Print Invoice" type="action" states="open,paid,proforma,sale,proforma2"/-->
@@ -437,7 +436,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
- <field name="message_ids" widget="mail_thread" placeholder="Share a note..."/>
+ <field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
@@ -448,11 +447,11 @@
<field name="model">account.invoice</field>
<field name="arch" type="xml">
<search string="Search Invoice">
- <field name="number" string="Invoice" filter_domain="['|','|', ('number','ilike',self), ('origin','ilike',self), ('supplier_invoice_number', 'ilike', self)]"/>
- <filter name="draft" icon="terp-document-new" string="Draft" domain="[('state','=','draft')]" help="Draft Invoices"/>
- <filter name="proforma" icon="terp-gtk-media-pause" string="Proforma" domain="[('state','=','proforma2')]" help="Proforma Invoices" groups="account.group_proforma_invoices"/>
- <filter name="invoices" icon="terp-dolar" string="Invoices" domain="[('state','not in',['draft','cancel'])]" help="Proforma/Open/Paid Invoices"/>
- <filter name="unpaid" icon="terp-dolar_ok!" string="Unpaid" domain="[('state','=','open')]" help="Unpaid Invoices"/>
+ <field name="number" string="Invoice" filter_domain="['|','|','|', ('number','ilike',self), ('origin','ilike',self), ('supplier_invoice_number', 'ilike', self), ('partner_id', 'ilike', self)]"/>
+ <filter name="draft" string="Draft" domain="[('state','=','draft')]" help="Draft Invoices"/>
+ <filter name="proforma" string="Proforma" domain="[('state','=','proforma2')]" help="Proforma Invoices" groups="account.group_proforma_invoices"/>
+ <filter name="invoices" string="Invoices" domain="[('state','not in',['draft','cancel'])]" help="Proforma/Open/Paid Invoices"/>
+ <filter name="unpaid" string="Unpaid" domain="[('state','=','open')]" help="Unpaid Invoices"/>
<separator/>
<filter domain="[('user_id','=',uid)]" help="My Invoices" icon="terp-personal"/>
<field name="partner_id"/>
diff --git a/addons/account/account_move_line.py b/addons/account/account_move_line.py
index f867688f179..a96ed889aae 100644
--- a/addons/account/account_move_line.py
+++ b/addons/account/account_move_line.py
@@ -780,7 +780,7 @@ class account_move_line(osv.osv):
else:
currency_id = line.company_id.currency_id
if line.reconcile_id:
- raise osv.except_osv(_('Warning!'), _('Already reconciled.'))
+ raise osv.except_osv(_('Warning'), _("Journal Item '%s' (id: %s), Move '%s' is already reconciled!") % (line.name, line.id, line.move_id.name))
if line.reconcile_partial_id:
for line2 in line.reconcile_partial_id.line_partial_ids:
if not line2.reconcile_id:
diff --git a/addons/account/account_view.xml b/addons/account/account_view.xml
index b4260db6b90..b8dcefdd3a4 100644
--- a/addons/account/account_view.xml
+++ b/addons/account/account_view.xml
@@ -161,7 +161,7 @@
</p>
</field>
</record>
- <menuitem id="menu_action_account_period" action="action_account_period" parent="account.next_id_23" groups="base.group_no_one"/>
+ <menuitem id="menu_action_account_period" action="action_account_period" parent="account.next_id_23"/>
<!-- Accounts -->
<record id="view_account_form" model="ir.ui.view">
@@ -435,15 +435,15 @@
<group string="Accounts">
<field name="profit_account_id" domain="[('type','!=','view')]"/>
<field name="loss_account_id" domain="[('type','!=','view')]"/>
- <field name="internal_account_id"/>
+ <field name="internal_account_id" domain="[('type','!=','view')]"/>
</group>
<group string="Miscellaneous">
<field name="with_last_closing_balance"/>
- <field name="cash_control"/>
+ <field name="cash_control" attrs="{'invisible':[('type','not in', ('cash',))]}"/>
</group>
</group>
- <separator string="Available Coins" colspan="4" attrs="{'invisible' : [('cash_control', '=', False)] }"/>
- <field name="cashbox_line_ids" nolabel="1" string="Unit Of Currency Definition" colspan="4" attrs="{'invisible' : [('cash_control', '=', False)]}">
+ <separator string="Available Coins" colspan="4" attrs="{'invisible' : ['|',('cash_control', '=', False),('type','not in', ('cash',))] }"/>
+ <field name="cashbox_line_ids" nolabel="1" string="Unit Of Currency Definition" colspan="4" attrs="{'invisible' : ['|',('cash_control', '=', False),('type','not in', ('cash',))]}">
<tree string="CashBox Lines" editable="bottom">
<field name="pieces" />
</tree>
@@ -552,7 +552,7 @@
<header>
<button name="button_confirm_bank" states="draft" string="Confirm" type="object" class="oe_highlight"/>
<button name="button_dummy" states="draft" string="Compute" type="object" class="oe_highlight"/>
- <button name="button_cancel" states="confirm" string="Cancel" type="object"/>
+ <button name="button_cancel" states="confirm" string="Cancel Statement" type="object"/>
<field name="state" widget="statusbar" statusbar_visible="draft,confirm"/>
</header>
<sheet>
@@ -1206,6 +1206,8 @@
<field name="name"/>
<field name="partner_id"/>
<field name="account_id"/>
+ <field name="period_id" invisible="1"/>
+ <field name="journal_id" invisible="1"/>
<field name="reconcile_partial_id"/>
<field name="state" invisible="1"/>
<field name="debit" sum="Total debit"/>
@@ -1256,7 +1258,7 @@
<form string="Account Entry" version="7.0">
<header>
<button name="button_validate" states="draft" string="Post" type="object" class="oe_highlight" groups="account.group_account_invoice"/>
- <button name="button_cancel" states="posted" string="Cancel" type="object" groups="account.group_account_invoice"/>
+ <button name="button_cancel" states="posted" string="Cancel Entry" type="object" groups="account.group_account_invoice"/>
<field name="state" widget="statusbar"/>
</header>
<sheet string="Journal Entries" >
@@ -2258,7 +2260,7 @@
<header>
<button name="button_confirm_cash" states="open" string="Close CashBox" type="object" class="oe_highlight"/>
<button name="button_open" states="draft" string="Open CashBox" type="object" class="oe_highlight"/>
- <button name="button_cancel" states="confirm,open" string="Cancel" type="object"/>
+ <button name="button_cancel" states="confirm,open" string="Cancel CashBox" type="object"/>
<field name="state" widget="statusbar" nolabel="1" statusbar_visible="draft,confirm"/>
</header>
<sheet string="Statement">
diff --git a/addons/account/partner_view.xml b/addons/account/partner_view.xml
index 8623bd03922..1267e20165b 100644
--- a/addons/account/partner_view.xml
+++ b/addons/account/partner_view.xml
@@ -95,6 +95,7 @@
</group>
<field name="bank_ids" context="{'default_partner_id': active_id, 'form_view_ref': 'base.view_partner_bank_form'}">
<tree string="Bank Details">
+ <field name="state" invisible="1"/>
<field name="sequence" invisible="1"/>
<field name="acc_number"/>
<field name="bank_name"/>
diff --git a/addons/account/project/project_view.xml b/addons/account/project/project_view.xml
index 62c8fb37075..aa567d2fc79 100644
--- a/addons/account/project/project_view.xml
+++ b/addons/account/project/project_view.xml
@@ -131,6 +131,7 @@
<field name="name"/>
<field name="account_id"/>
<field name="journal_id"/>
+ <field name="user_id"/>
</group>
<group>
<field name="date"/>
@@ -169,6 +170,7 @@
<field name="date"/>
<field name="ref" invisible="context.get('to_invoice', False)"/>
<field name="name"/>
+ <field name="user_id"/>
<field name="journal_id" invisible="context.get('to_invoice', False)"/>
<field name="amount" sum="Total" invisible="context.get('to_invoice', False)"/>
<field name="product_id" on_change="on_change_unit_amount(product_id, unit_amount, company_id, product_uom_id, journal_id)" invisible="not context.get('to_invoice', False)"/>
@@ -176,7 +178,6 @@
<field name="product_uom_id" on_change="on_change_unit_amount(product_id, unit_amount, company_id, product_uom_id)" invisible="not context.get('to_invoice', False)"/>
<field domain="[('type','=','normal')]" name="account_id"/>
<field name="general_account_id" invisible="context.get('to_invoice', False)"/>
- <field name="user_id" invisible="1" />
<field name="company_id" groups="base.group_multi_company"/>
</tree>
</field>
diff --git a/addons/account/report/account_financial_report.py b/addons/account/report/account_financial_report.py
index d94955a764a..864c4bbc7d1 100644
--- a/addons/account/report/account_financial_report.py
+++ b/addons/account/report/account_financial_report.py
@@ -65,7 +65,7 @@ class report_account_common(report_sxw.rml_parse, common_report_header):
vals['debit'] = report.debit
vals['credit'] = report.credit
if data['form']['enable_filter']:
- vals['balance_cmp'] = self.pool.get('account.financial.report').browse(self.cr, self.uid, report.id, context=data['form']['comparison_context']).balance
+ vals['balance_cmp'] = self.pool.get('account.financial.report').browse(self.cr, self.uid, report.id, context=data['form']['comparison_context']).balance * report.sign or 0.0
lines.append(vals)
account_ids = []
if report.display_detail == 'no_detail':
@@ -97,7 +97,7 @@ class report_account_common(report_sxw.rml_parse, common_report_header):
if not currency_obj.is_zero(self.cr, self.uid, account.company_id.currency_id, vals['balance']):
flag = True
if data['form']['enable_filter']:
- vals['balance_cmp'] = account_obj.browse(self.cr, self.uid, account.id, context=data['form']['comparison_context']).balance
+ vals['balance_cmp'] = account_obj.browse(self.cr, self.uid, account.id, context=data['form']['comparison_context']).balance * report.sign or 0.0
if not currency_obj.is_zero(self.cr, self.uid, account.company_id.currency_id, vals['balance_cmp']):
flag = True
if flag:
diff --git a/addons/account_analytic_analysis/account_analytic_analysis.py b/addons/account_analytic_analysis/account_analytic_analysis.py
index 0a56c80766b..fe47b5d293b 100644
--- a/addons/account_analytic_analysis/account_analytic_analysis.py
+++ b/addons/account_analytic_analysis/account_analytic_analysis.py
@@ -714,6 +714,17 @@ class account_analytic_account(osv.osv):
self.write(cr, uid, [contract.id], {'recurring_next_date': new_date.strftime('%Y-%m-%d')}, context=context)
return True
+ def onchange_invoice_on_timesheets(self, cr, uid, ids, invoice_on_timesheets, context=None):
+ if not invoice_on_timesheets:
+ return {}
+ result = {'value': {'use_timesheets': True}}
+ try:
+ to_invoice = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'hr_timesheet_invoice', 'timesheet_invoice_factor1')
+ result['value']['to_invoice'] = to_invoice[1]
+ except ValueError:
+ pass
+ return result
+
class account_analytic_account_summary_user(osv.osv):
_name = "account_analytic_analysis.summary.user"
_description = "Hours Summary by User"
diff --git a/addons/account_analytic_analysis/account_analytic_analysis_cron.xml b/addons/account_analytic_analysis/account_analytic_analysis_cron.xml
index 14ca1fd2135..970ab275ef9 100644
--- a/addons/account_analytic_analysis/account_analytic_analysis_cron.xml
+++ b/addons/account_analytic_analysis/account_analytic_analysis_cron.xml
@@ -55,6 +55,19 @@ Hello ${object.name},
${account_table(ctx["data"]["future"].iteritems())}
% endif
+<p>
+ You can check all contracts to be renewed using the menu:
+</p>
+<ul>
+ <li>Sales / Invoicing / Contracts to Renew</li>
+</ul>
+<p>
+ Thanks,
+</p>
+
+<pre>
+--
+OpenERP Automatic Email
</pre>
]]></field>
diff --git a/addons/account_analytic_analysis/account_analytic_analysis_view.xml b/addons/account_analytic_analysis/account_analytic_analysis_view.xml
index b4746484e7c..3c477b5556f 100644
--- a/addons/account_analytic_analysis/account_analytic_analysis_view.xml
+++ b/addons/account_analytic_analysis/account_analytic_analysis_view.xml
@@ -130,11 +130,11 @@
</tr>
</table>
<group name='invoice_on_timesheets'>
- <p class="oe_grey oe_edit_only" colspan="2" attrs="{'invisible': [('invoice_on_timesheets','=',False)]}">
- When invoicing on timesheet, OpenERP uses the
+ <p name='invoice_on_timesheets_label' class="oe_grey oe_edit_only" colspan="2" attrs="{'invisible': [('invoice_on_timesheets','=',False)]}">
+ When reinvoicing costs, OpenERP uses the
pricelist of the contract which uses the price
- defined on the product related to each employee to
- define the customer invoice price rate.
+ defined on the product related (e.g timesheet
+ products are defined on each employee).
</p>
<group>
<field name="pricelist_id"
@@ -218,7 +218,7 @@
<field name="parent_id"/>
<filter name="open" string="In Progress" domain="[('state','in',('open','draft'))]" help="Contracts in progress (open, draft)"/>
<filter name="pending" string="To Renew" domain="[('state','=','pending')]" help="Pending contracts"/>
- <filter name="closed" string="Closed" domain="[('state','=','pending')]" help="Closed contracts"/>
+ <filter name="closed" string="Closed" domain="[('state','=','close')]" help="Closed contracts"/>
<filter name="cancelled" string="Cancelled" domain="[('state','=','cancel')]" help="Cancelled contracts"/>
<separator/>
<filter
diff --git a/addons/account_analytic_plans/account_analytic_plans.py b/addons/account_analytic_plans/account_analytic_plans.py
index 99cb0c4ebc4..5834db99cd3 100644
--- a/addons/account_analytic_plans/account_analytic_plans.py
+++ b/addons/account_analytic_plans/account_analytic_plans.py
@@ -308,8 +308,8 @@ class account_invoice_line(osv.osv):
res ['analytics_id'] = line.analytics_id and line.analytics_id.id or False
return res
- def product_id_change(self, cr, uid, ids, product, uom, qty=0, name='', type='out_invoice', partner_id=False, fposition_id=False, price_unit=False, currency_id=False, context=None, company_id=None):
- res_prod = super(account_invoice_line, self).product_id_change(cr, uid, ids, product, uom, qty, name, type, partner_id, fposition_id, price_unit, currency_id, context=context, company_id=company_id)
+ def product_id_change(self, cr, uid, ids, product, uom_id, qty=0, name='', type='out_invoice', partner_id=False, fposition_id=False, price_unit=False, currency_id=False, context=None, company_id=None):
+ res_prod = super(account_invoice_line, self).product_id_change(cr, uid, ids, product, uom_id, qty, name, type, partner_id, fposition_id, price_unit, currency_id, context=context, company_id=company_id)
rec = self.pool.get('account.analytic.default').account_get(cr, uid, product, partner_id, uid, time.strftime('%Y-%m-%d'), context=context)
if rec and rec.analytics_id:
res_prod['value'].update({'analytics_id': rec.analytics_id.id})
diff --git a/addons/account_asset/account_asset.py b/addons/account_asset/account_asset.py
index b335063b758..5a22a888772 100644
--- a/addons/account_asset/account_asset.py
+++ b/addons/account_asset/account_asset.py
@@ -25,6 +25,7 @@ from dateutil.relativedelta import relativedelta
from openerp.osv import fields, osv
import openerp.addons.decimal_precision as dp
+from openerp.tools.translate import _
class account_asset_category(osv.osv):
_name = 'account.asset.category'
@@ -75,6 +76,12 @@ class account_asset_asset(osv.osv):
_name = 'account.asset.asset'
_description = 'Asset'
+ def unlink(self, cr, uid, ids, context=None):
+ for asset in self.browse(cr, uid, ids, context=context):
+ if asset.account_move_line_ids:
+ raise osv.except_osv(_('Error!'), _('You cannot delete an asset that contains posted depreciation lines.'))
+ return super(account_account, self).unlink(cr, uid, ids, context=context)
+
def _get_period(self, cr, uid, context=None):
periods = self.pool.get('account.period').find(cr, uid)
if periods:
@@ -369,7 +376,7 @@ class account_asset_depreciation_line(osv.osv):
_columns = {
'name': fields.char('Depreciation Name', size=64, required=True, select=1),
'sequence': fields.integer('Sequence', required=True),
- 'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True),
+ 'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True, ondelete='cascade'),
'parent_state': fields.related('asset_id', 'state', type='char', string='State of Asset'),
'amount': fields.float('Current Depreciation', digits_compute=dp.get_precision('Account'), required=True),
'remaining_value': fields.float('Next Period Depreciation', digits_compute=dp.get_precision('Account'),required=True),
@@ -454,7 +461,7 @@ account_asset_depreciation_line()
class account_move_line(osv.osv):
_inherit = 'account.move.line'
_columns = {
- 'asset_id': fields.many2one('account.asset.asset', 'Asset'),
+ 'asset_id': fields.many2one('account.asset.asset', 'Asset', ondelete="restrict"),
'entry_ids': fields.one2many('account.move.line', 'asset_id', 'Entries', readonly=True, states={'draft':[('readonly',False)]}),
}
diff --git a/addons/account_budget/account_budget_view.xml b/addons/account_budget/account_budget_view.xml
index c76c934828e..9ad61d7c286 100644
--- a/addons/account_budget/account_budget_view.xml
+++ b/addons/account_budget/account_budget_view.xml
@@ -99,7 +99,7 @@
<button string="Approve" name="validate" states="confirm" type="workflow" class="oe_highlight"/>
<button string="Done" name="done" states="validate" type="workflow" class="oe_highlight"/>
<button name="draft" states="cancel" string="Reset to Draft" type="workflow" />
- <button string="Cancel" name="cancel" states="confirm,validate" type="workflow"/>
+ <button string="Cancel Budget" name="cancel" states="confirm,validate" type="workflow"/>
<field name="state" widget="statusbar" statusbar_visible="draft,confirm"/>
</header>
<sheet string="Budget">
diff --git a/addons/account_cancel/account_cancel_view.xml b/addons/account_cancel/account_cancel_view.xml
index 4ed8093a0aa..37e9dcd1ee8 100644
--- a/addons/account_cancel/account_cancel_view.xml
+++ b/addons/account_cancel/account_cancel_view.xml
@@ -18,7 +18,7 @@
<field name="inherit_id" ref="account.invoice_form"/>
<field name="arch" type="xml">
<xpath expr="//button[@name='invoice_cancel']" position="replace">
- <button name="invoice_cancel" states="draft,proforma2,sale,open" string="Cancel" groups="account.group_account_invoice"/>
+ <button name="invoice_cancel" states="draft,proforma2,sale,open" string="Cancel Invoice" groups="account.group_account_invoice"/>
</xpath>
</field>
</record>
@@ -29,7 +29,7 @@
<field name="inherit_id" ref="account.invoice_supplier_form"/>
<field name="arch" type="xml">
<xpath expr="//button[@name='invoice_cancel']" position="replace">
- <button name="invoice_cancel" states="draft,proforma2,sale,open" string="Cancel" groups="account.group_account_invoice"/>
+ <button name="invoice_cancel" states="draft,proforma2,sale,open" string="Cancel Invoice" groups="account.group_account_invoice"/>
</xpath>
</field>
</record>
diff --git a/addons/account_followup/account_followup.py b/addons/account_followup/account_followup.py
index a4e8f874332..036feaaf0e5 100644
--- a/addons/account_followup/account_followup.py
+++ b/addons/account_followup/account_followup.py
@@ -292,8 +292,7 @@ class res_partner(osv.osv):
type = 'comment',
subtype = "mail.mt_comment", context = context,
model = 'res.partner', res_id = part.id,
- notified_partner_ids = [(6, 0, [responsible_partner_id])],
- partner_ids = [(6, 0, [responsible_partner_id])])
+ partner_ids = [responsible_partner_id])
return super(res_partner, self).write(cr, uid, ids, vals, context=context)
def action_done(self, cr, uid, ids, context=None):
diff --git a/addons/account_followup/account_followup_customers.xml b/addons/account_followup/account_followup_customers.xml
index 78a9c892614..6be11399b68 100644
--- a/addons/account_followup/account_followup_customers.xml
+++ b/addons/account_followup/account_followup_customers.xml
@@ -24,6 +24,17 @@
</field>
</record>
+ <record id="view_partner_inherit_customer_followup_tree" model="ir.ui.view">
+ <field name="name">res.partner.followup.inherit.tree</field>
+ <field name="model">res.partner</field>
+ <field name="inherit_id" ref="base.view_partner_tree"/>
+ <field name="arch" type="xml">
+ <field name="name" position="after">
+ <field name="payment_responsible_id" invisible="1"/>
+ </field>
+ </field>
+ </record>
+
<record id="customer_followup_search_view" model="ir.ui.view">
<field name="name">Search</field>
<field name="model">res.partner</field>
@@ -40,7 +51,7 @@
</group>
<group expand="1" string="Group By...">
<filter string="Follow-up Responsible" context="{'group_by':'payment_responsible_id'}"/>
- <filter String="Followup Level" context="{'group_by':'latest_followup_level_id'}"/>
+ <filter string="Followup Level" context="{'group_by':'latest_followup_level_id'}"/>
</group>
</search>
</field>
diff --git a/addons/account_followup/tests/test_account_followup.py b/addons/account_followup/tests/test_account_followup.py
index 8ddf3f82288..a421931665a 100644
--- a/addons/account_followup/tests/test_account_followup.py
+++ b/addons/account_followup/tests/test_account_followup.py
@@ -52,10 +52,10 @@ class TestAccountFollowup(TransactionCase):
def test_00_send_followup_after_3_days(self):
""" Send follow up after 3 days and check nothing is done (as first follow-up level is only after 15 days)"""
cr, uid = self.cr, self.uid
- current_date = datetime.datetime.now()
+ current_date = datetime.datetime.utcnow()
delta = datetime.timedelta(days=3)
result = current_date + delta
- self.wizard_id = self.wizard.create(cr, uid, {'date':result.strftime("%Y-%m-%d"),
+ self.wizard_id = self.wizard.create(cr, uid, {'date':result.strftime(tools.DEFAULT_SERVER_DATE_FORMAT),
'followup_id': self.followup_id
}, context={"followup_id": self.followup_id})
self.wizard.do_process(cr, uid, [self.wizard_id], context={"followup_id": self.followup_id})
@@ -64,34 +64,33 @@ class TestAccountFollowup(TransactionCase):
def run_wizard_three_times(self):
cr, uid = self.cr, self.uid
- current_date = datetime.datetime.now()
+ current_date = datetime.datetime.utcnow()
delta = datetime.timedelta(days=40)
result = current_date + delta
- self.wizard_id = self.wizard.create(cr, uid, {'date':result.strftime("%Y-%m-%d"),
+ self.wizard_id = self.wizard.create(cr, uid, {'date':result.strftime(tools.DEFAULT_SERVER_DATE_FORMAT),
'followup_id': self.followup_id
}, context={"followup_id": self.followup_id})
- self.wizard.do_process(cr, uid, [self.wizard_id], context={"followup_id": self.followup_id})
- self.wizard_id = self.wizard.create(cr, uid, {'date':result.strftime("%Y-%m-%d"),
+ self.wizard.do_process(cr, uid, [self.wizard_id], context={"followup_id": self.followup_id, 'tz':'UTC'})
+ self.wizard_id = self.wizard.create(cr, uid, {'date':result.strftime(tools.DEFAULT_SERVER_DATE_FORMAT),
'followup_id': self.followup_id
}, context={"followup_id": self.followup_id})
- self.wizard.do_process(cr, uid, [self.wizard_id], context={"followup_id": self.followup_id})
- self.wizard_id = self.wizard.create(cr, uid, {'date':result.strftime("%Y-%m-%d"),
- 'followup_id': self.followup_id
+ self.wizard.do_process(cr, uid, [self.wizard_id], context={"followup_id": self.followup_id, 'tz':'UTC'})
+ self.wizard_id = self.wizard.create(cr, uid, {'date':result.strftime(tools.DEFAULT_SERVER_DATE_FORMAT),
+ 'followup_id': self.followup_id,
}, context={"followup_id": self.followup_id})
- self.wizard.do_process(cr, uid, [self.wizard_id], context={"followup_id": self.followup_id})
-
+ self.wizard.do_process(cr, uid, [self.wizard_id], context={"followup_id": self.followup_id, 'tz':'UTC'})
def test_01_send_followup_later_for_upgrade(self):
""" Send one follow-up after 15 days to check it upgrades to level 1"""
cr, uid = self.cr, self.uid
- current_date = datetime.datetime.now()
+ current_date = datetime.datetime.utcnow()
delta = datetime.timedelta(days=15)
result = current_date + delta
self.wizard_id = self.wizard.create(cr, uid, {
- 'date':result.strftime("%Y-%m-%d"),
+ 'date':result.strftime(tools.DEFAULT_SERVER_DATE_FORMAT),
'followup_id': self.followup_id
}, context={"followup_id": self.followup_id})
- self.wizard.do_process(cr, uid, [self.wizard_id], context={"followup_id": self.followup_id})
+ self.wizard.do_process(cr, uid, [self.wizard_id], context={"followup_id": self.followup_id, 'tz':'UTC'})
self.assertEqual(self.partner.browse(cr, uid, self.partner_id).latest_followup_level_id.id, self.first_followup_line_id,
"Not updated to the correct follow-up level")
@@ -102,12 +101,12 @@ class TestAccountFollowup(TransactionCase):
self.assertEqual(self.partner.browse(cr, uid, self.partner_id).payment_next_action,
"Call the customer on the phone! ", "Manual action not set")
self.assertEqual(self.partner.browse(cr, uid, self.partner_id).payment_next_action_date,
- datetime.datetime.now().strftime("%Y-%m-%d"))
-
+ datetime.datetime.utcnow().strftime(tools.DEFAULT_SERVER_DATE_FORMAT))
+
def test_03_filter_on_credit(self):
""" Check the partners can be filtered on having credits """
cr, uid = self.cr, self.uid
- ids = self.partner.search(cr, uid, [('payment_amount_due', '>=', 0.0)])
+ ids = self.partner.search(cr, uid, [('payment_amount_due', '>', 0.0)])
self.assertIn(self.partner_id, ids)
def test_04_action_done(self):
@@ -139,7 +138,7 @@ class TestAccountFollowup(TransactionCase):
"""Run wizard until manual action, pay the invoice and check that partner has no follow-up level anymore and after running the wizard the action is empty"""
cr, uid = self.cr, self.uid
self.test_02_check_manual_action()
- current_date = datetime.datetime.now()
+ current_date = datetime.datetime.utcnow()
delta = datetime.timedelta(days=1)
result = current_date + delta
self.invoice.pay_and_reconcile(cr, uid, [self.invoice_id], 1000.0, self.pay_account_id,
@@ -147,7 +146,7 @@ class TestAccountFollowup(TransactionCase):
self.period_id, self.journal_id,
name = "Payment for test customer invoice follow-up")
self.assertFalse(self.partner.browse(cr, uid, self.partner_id).latest_followup_level_id, "Level not empty")
- self.wizard_id = self.wizard.create(cr, uid, {'date':result.strftime("%Y-%m-%d"),
+ self.wizard_id = self.wizard.create(cr, uid, {'date':result.strftime(tools.DEFAULT_SERVER_DATE_FORMAT),
'followup_id': self.followup_id
}, context={"followup_id": self.followup_id})
self.wizard.do_process(cr, uid, [self.wizard_id], context={"followup_id": self.followup_id})
diff --git a/addons/account_payment/account_payment_view.xml b/addons/account_payment/account_payment_view.xml
index e07043c495a..c2f27a576a2 100644
--- a/addons/account_payment/account_payment_view.xml
+++ b/addons/account_payment/account_payment_view.xml
@@ -88,7 +88,7 @@
<button name="open" states="draft" string="Confirm Payments" class="oe_highlight"/>
<button name="set_done" states="open" string="Make Payments" type="object" class="oe_highlight"/>
<button name="set_to_draft" states="cancel" string="Set to draft" type="object"/>
- <button name="cancel" states="draft,open" string="Cancel"/>
+ <button name="cancel" states="draft,open" string="Cancel Payments"/>
<field name="state" widget="statusbar" statusbar_visible="draft,open"/>
</header>
<sheet string="Payment order">
diff --git a/addons/account_voucher/account_voucher_view.xml b/addons/account_voucher/account_voucher_view.xml
index 1e1481c3f2b..f429480f656 100644
--- a/addons/account_voucher/account_voucher_view.xml
+++ b/addons/account_voucher/account_voucher_view.xml
@@ -43,8 +43,8 @@
<form string="Accounting Voucher" version="7.0">
<header>
<button name="proforma_voucher" string="Post" states="draft" class="oe_highlight"/>
- <button name="cancel_voucher" string="Cancel" type="object" states="posted" confirm="Are you sure to unreconcile this record?"/>
- <button name="cancel_voucher" string="Cancel" states="draft,proforma" />
+ <button name="cancel_voucher" string="Cancel Voucher" type="object" states="posted" confirm="Are you sure you want to unreconcile this record?"/>
+ <button name="cancel_voucher" string="Cancel Voucher" states="draft,proforma" />
<button name="action_cancel_draft" type="object" states="cancel" string="Set to Draft"/>
<field name="state" widget="statusbar" statusbar_visible="draft,posted" statusbar_colors='{"proforma":"blue"}'/>
</header>
diff --git a/addons/account_voucher/invoice.py b/addons/account_voucher/invoice.py
index bd43694260b..c0584ddbd52 100644
--- a/addons/account_voucher/invoice.py
+++ b/addons/account_voucher/invoice.py
@@ -41,7 +41,7 @@ class invoice(osv.osv):
'target': 'new',
'domain': '[]',
'context': {
- 'default_partner_id': self._find_partner(inv).id,
+ 'default_partner_id': self.pool.get('res.partner')._find_accounting_partner(inv.partner_id).id,
'default_amount': inv.type in ('out_refund', 'in_refund') and -inv.residual or inv.residual,
'default_reference': inv.name,
'close_after_process': True,
diff --git a/addons/account_voucher/voucher_payment_receipt_view.xml b/addons/account_voucher/voucher_payment_receipt_view.xml
index b869f6c83b2..de873c9195c 100644
--- a/addons/account_voucher/voucher_payment_receipt_view.xml
+++ b/addons/account_voucher/voucher_payment_receipt_view.xml
@@ -126,7 +126,7 @@
<form string="Voucher Payment" version="7.0">
<header>
<button name="proforma_voucher" string="Validate" states="draft" invisible="context.get('line_type', False)" class="oe_highlight"/>
- <button name="cancel_voucher" string="Cancel" states="draft,proforma" invisible="context.get('line_type', False)"/>
+ <button name="cancel_voucher" string="Cancel Voucher" states="draft,proforma" invisible="context.get('line_type', False)"/>
<button name="cancel_voucher" string="Unreconcile" type="object" states="posted" invisible="context.get('line_type', False)" confirm="Are you sure to unreconcile and cancel this record ?"/>
<button name="action_cancel_draft" type="object" states="cancel" string="Set to Draft" invisible="context.get('line_type', False)"/>
<field name="state" widget="statusbar" statusbar_visible="draft,posted" statusbar_colors='{"proforma":"blue"}'/>
@@ -394,7 +394,7 @@
<form string="Receipt" version="7.0">
<header invisible="context.get('line_type', False)">
<button name="proforma_voucher" string="Validate" states="draft" class="oe_highlight"/>
- <button name="cancel_voucher" string="Cancel" states="draft,proforma"/>
+ <button name="cancel_voucher" string="Cancel Receipt" states="draft,proforma"/>
<button name="cancel_voucher" string="Unreconcile" type="object" states="posted" confirm="Are you sure to unreconcile and cancel this record ?"/>
<button name="action_cancel_draft" type="object" states="cancel" string="Set to Draft"/>
<field name="state" widget="statusbar" statusbar_visible="draft,posted" statusbar_colors='{"proforma":"blue"}'/>
diff --git a/addons/account_voucher/voucher_sales_purchase_view.xml b/addons/account_voucher/voucher_sales_purchase_view.xml
index 9a3841a13f0..05b783dd339 100644
--- a/addons/account_voucher/voucher_sales_purchase_view.xml
+++ b/addons/account_voucher/voucher_sales_purchase_view.xml
@@ -59,17 +59,17 @@
<field name="name">account.voucher.sale.form</field>
<field name="model">account.voucher</field>
<field name="arch" type="xml">
- <form string="Sale voucher" version="7.0">
+ <form string="Sales Receipt" version="7.0">
<header>
<button name="proforma_voucher" string="Validate" states="draft" class="oe_highlight"/>
<button name="%(act_pay_voucher)d" context="{'narration':narration, 'title':'Customer Payment', 'type':'receipt', 'partner_id':partner_id, 'reference':reference, 'amount':amount}" type="action" string="Validate Payment" attrs="{'invisible':['|',('pay_now','=','pay_now'),'|',('state','=','draft'), ('paid','=',True)]}" class="oe_highlight"/>
- <button name="cancel_voucher" string="Cancel" states="draft,proforma" />
- <button name="cancel_voucher" string="Cancel" type="object" states="posted" confirm="Are you sure you want to cancel this receipt?"/>
+ <button name="cancel_voucher" string="Cancel Receipt" states="draft,proforma" />
+ <button name="cancel_voucher" string="Cancel Receipt" type="object" states="posted" confirm="Are you sure you want to cancel this receipt?"/>
<button name="action_cancel_draft" type="object" states="cancel" string="Set to Draft"/>
<field name="state" widget="statusbar" statusbar_visible="draft,posted" statusbar_colors='{"proforma":"blue"}'/>
</header>
- <sheet string="Sales Receipts" >
- <h1><label for="number" string="Sale Receipt"/> <field name="number" class="oe_inline" readonly="1"/></h1>
+ <sheet string="Sales Receipt" >
+ <h1><label for="number" string="Sales Receipt"/> <field name="number" class="oe_inline" readonly="1"/></h1>
<group>
<group>
<field name="type" invisible="True"/>
@@ -208,8 +208,8 @@
<header>
<button name="proforma_voucher" string="Validate" states="draft" class="oe_highlight"/>
<button name="%(act_pay_bills)d" context="{'narration':narration, 'title':'Bill Payment', 'type':'payment', 'partner_id': partner_id, 'reference':reference}" type="action" string="Pay Bill" attrs="{'invisible':['|',('pay_now','=','pay_now'),'|',('state','=','draft'), ('paid','=',True)]}" class="oe_highlight"/>
- <button name="cancel_voucher" string="Cancel" states="draft,proforma" />
- <button name="cancel_voucher" string="Cancel" type="object" states="posted" confirm="Are you sure you want to cancel this receipt?"/>
+ <button name="cancel_voucher" string="Cancel Voucher" states="draft,proforma" />
+ <button name="cancel_voucher" string="Cancel Voucher" type="object" states="posted" confirm="Are you sure you want to cancel this receipt?"/>
<button name="action_cancel_draft" type="object" states="cancel" string="Set to Draft"/>
<field name="state" widget="statusbar" statusbar_visible="draft,posted" statusbar_colors='{"proforma":"blue"}'/>
</header>
diff --git a/addons/analytic_contract_hr_expense/analytic_contract_hr_expense_view.xml b/addons/analytic_contract_hr_expense/analytic_contract_hr_expense_view.xml
index 5628846a6a0..db269ef37c7 100644
--- a/addons/analytic_contract_hr_expense/analytic_contract_hr_expense_view.xml
+++ b/addons/analytic_contract_hr_expense/analytic_contract_hr_expense_view.xml
@@ -40,6 +40,16 @@
</td>
</tr>
</xpath>
+ <xpath expr="//p[@name='invoice_on_timesheets_label']" position="attributes">
+ <attribute name="attrs">{'invisible': [('invoice_on_timesheets','=',False),('charge_expenses','=',False)]}</attribute>
+ </xpath>
+ <xpath expr="//field[@name='pricelist_id']" position="attributes">
+ <attribute name="attrs">{'required': ['|',('invoice_on_timesheets','=',True),('charge_expenses','=',True)], 'invisible':[('invoice_on_timesheets','=',False), ('charge_expenses','=',False)]}</attribute>
+ </xpath>
+ <xpath expr="//field[@name='to_invoice']" position="attributes">
+ <attribute name="attrs">{'required': ['|',('invoice_on_timesheets','=',True),('charge_expenses','=',True)]}</attribute>
+ <attribute name="string">Expenses and Timesheet Invoicing Ratio</attribute>
+ </xpath>
</field>
</record>
diff --git a/addons/auth_signup/__openerp__.py b/addons/auth_signup/__openerp__.py
index 4e1ada7bd19..08740962c59 100644
--- a/addons/auth_signup/__openerp__.py
+++ b/addons/auth_signup/__openerp__.py
@@ -42,4 +42,5 @@ Allow users to sign up and reset their password
],
'js': ['static/src/js/auth_signup.js'],
'qweb': ['static/src/xml/auth_signup.xml'],
+ 'bootstrap': True,
}
diff --git a/addons/auth_signup/controllers/main.py b/addons/auth_signup/controllers/main.py
index baf1a8f0b28..924e1546862 100644
--- a/addons/auth_signup/controllers/main.py
+++ b/addons/auth_signup/controllers/main.py
@@ -19,9 +19,6 @@
#
##############################################################################
import logging
-import urllib
-
-import werkzeug
import openerp
from openerp.modules.registry import RegistryManager
@@ -54,9 +51,8 @@ class Controller(openerp.addons.web.http.Controller):
return user_info
@openerp.addons.web.http.jsonrequest
- def signup(self, req, dbname, token, name, login, password):
+ def signup(self, req, dbname, token, **values):
""" sign up a user (new or existing)"""
- values = {'name': name, 'login': login, 'password': password}
try:
self._signup_with_values(req, dbname, token, values)
except SignupError, e:
@@ -69,7 +65,7 @@ class Controller(openerp.addons.web.http.Controller):
res_users = registry.get('res.users')
res_users.signup(cr, openerp.SUPERUSER_ID, values, token)
- @openerp.addons.web.http.httprequest
+ @openerp.addons.web.http.jsonrequest
def reset_password(self, req, dbname, login):
""" retrieve user, and perform reset password """
registry = RegistryManager.get(dbname)
@@ -78,12 +74,10 @@ class Controller(openerp.addons.web.http.Controller):
res_users = registry.get('res.users')
res_users.reset_password(cr, openerp.SUPERUSER_ID, login)
cr.commit()
- message = 'An email has been sent with credentials to reset your password'
except Exception as e:
# signup error
_logger.exception('error when resetting password')
- message = e.message
- params = [('action', 'login'), ('error_message', message)]
- return werkzeug.utils.redirect("/#" + urllib.urlencode(params))
+ raise(e)
+ return True
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/auth_signup/static/src/js/auth_signup.js b/addons/auth_signup/static/src/js/auth_signup.js
index 6757b9cb69d..fe071fde55d 100644
--- a/addons/auth_signup/static/src/js/auth_signup.js
+++ b/addons/auth_signup/static/src/js/auth_signup.js
@@ -5,32 +5,33 @@ openerp.auth_signup = function(instance) {
instance.web.Login.include({
start: function() {
var self = this;
- var d = this._super();
- d.done(function() {
- self.$(".oe_signup_show").hide();
+ this.signup_enabled = false;
+ this.reset_password_enabled = false;
+ return this._super().then(function() {
+
+ // Switches the login box to the select mode whith mode == [default|signup|reset]
+ self.on('change:login_mode', self, function() {
+ var mode = self.get('login_mode') || 'default';
+ self.$('*[data-modes]').each(function() {
+ var modes = $(this).data('modes').split(/\s+/);
+ $(this).toggle(modes.indexOf(mode) > -1);
+ });
+ self.$('a.oe_signup_signup:visible').toggle(self.signup_enabled);
+ self.$('a.oe_signup_reset_password:visible').toggle(self.reset_password_enabled);
+ });
+
// to switch between the signup and regular login form
self.$('a.oe_signup_signup').click(function(ev) {
- if (ev) {
- ev.preventDefault();
- }
- self.$el.addClass("oe_login_signup");
- self.$(".oe_signup_show").show();
- self.$(".oe_signup_hide").hide();
+ self.set('login_mode', 'signup');
return false;
});
self.$('a.oe_signup_back').click(function(ev) {
- if (ev) {
- ev.preventDefault();
- }
- self.$el.removeClass("oe_login_signup");
- self.$(".oe_signup_show").hide();
- self.$(".oe_signup_hide").show();
+ self.set('login_mode', 'default');
delete self.params.token;
return false;
});
- var dblist = self.db_list || [];
- var dbname = self.params.db || (dblist.length === 1 ? dblist[0] : null);
+ var dbname = self.selected_db;
// if there is an error message in params, show it then forget it
if (self.params.error_message) {
@@ -42,7 +43,7 @@ openerp.auth_signup = function(instance) {
if (dbname && self.params.token) {
self.rpc("/auth_signup/retrieve", {dbname: dbname, token: self.params.token})
.done(self.on_token_loaded)
- .fail(self.on_token_failed)
+ .fail(self.on_token_failed);
}
if (dbname && self.params.login) {
self.$("form input[name=login]").val(self.params.login);
@@ -51,23 +52,21 @@ openerp.auth_signup = function(instance) {
// bind reset password link
self.$('a.oe_signup_reset_password').click(self.do_reset_password);
- // make signup link and reset password link visible only when enabled
- self.$('a.oe_signup_signup').hide();
- self.$('a.oe_signup_reset_password').hide();
if (dbname) {
- self.rpc("/auth_signup/get_config", {dbname: dbname})
- .done(function(result) {
- if (result.signup) {
- self.$('a.oe_signup_signup').show();
- }
- if (result.reset_password) {
- self.$('a.oe_signup_reset_password').show();
- }
- });
+ self.rpc("/auth_signup/get_config", {dbname: dbname}).done(function(result) {
+ self.signup_enabled = result.signup;
+ self.reset_password_enabled = result.reset_password;
+ if (self.$("form input[name=login]").val()){
+ self.set('login_mode', 'default');
+ } else {
+ self.set('login_mode', 'signup');
+ }
+ });
+ } else {
+ // TODO: support multiple database mode
+ self.set('login_mode', 'default');
}
});
-
- return d;
},
on_token_loaded: function(result) {
@@ -76,9 +75,7 @@ openerp.auth_signup = function(instance) {
this.on_db_loaded([result.db]);
if (result.token) {
// switch to signup mode, set user name and login
- this.$el.addClass("oe_login_signup");
- self.$(".oe_signup_show").show();
- self.$(".oe_signup_hide").hide();
+ this.set('login_mode', (this.params.type === 'reset' ? 'reset' : 'signup'));
this.$("form input[name=name]").val(result.name).attr("readonly", "readonly");
if (result.login) {
this.$("form input[name=login]").val(result.login).attr("readonly", "readonly");
@@ -88,6 +85,7 @@ openerp.auth_signup = function(instance) {
} else {
// remain in login mode, set login if present
delete this.params.token;
+ this.set('login_mode', 'default');
this.$("form input[name=login]").val(result.login || "");
}
},
@@ -99,43 +97,52 @@ openerp.auth_signup = function(instance) {
this.show_error(_t("Invalid signup token"));
delete this.params.db;
delete this.params.token;
+ this.set('login_mode', 'default');
+ },
+
+ get_params: function(){
+ // signup user (or reset password)
+ var db = this.$("form [name=db]").val();
+ var name = this.$("form input[name=name]").val();
+ var login = this.$("form input[name=login]").val();
+ var password = this.$("form input[name=password]").val();
+ var confirm_password = this.$("form input[name=confirm_password]").val();
+ if (!db) {
+ this.do_warn(_t("Login"), _t("No database selected !"));
+ return false;
+ } else if (!name) {
+ this.do_warn(_t("Login"), _t("Please enter a name."));
+ return false;
+ } else if (!login) {
+ this.do_warn(_t("Login"), _t("Please enter a username."));
+ return false;
+ } else if (!password || !confirm_password) {
+ this.do_warn(_t("Login"), _t("Please enter a password and confirm it."));
+ return false;
+ } else if (password !== confirm_password) {
+ this.do_warn(_t("Login"), _t("Passwords do not match; please retype them."));
+ return false;
+ }
+ var params = {
+ dbname : db,
+ token: this.params.token || "",
+ name: name,
+ login: login,
+ password: password,
+ };
+ return params;
},
on_submit: function(ev) {
if (ev) {
ev.preventDefault();
}
- if (this.$el.hasClass("oe_login_signup")) {
- // signup user (or reset password)
- var db = this.$("form [name=db]").val();
- var name = this.$("form input[name=name]").val();
- var login = this.$("form input[name=login]").val();
- var password = this.$("form input[name=password]").val();
- var confirm_password = this.$("form input[name=confirm_password]").val();
- if (!db) {
- this.do_warn(_t("Login"), _t("No database selected !"));
- return false;
- } else if (!name) {
- this.do_warn(_t("Login"), _t("Please enter a name."));
- return false;
- } else if (!login) {
- this.do_warn(_t("Login"), _t("Please enter a username."));
- return false;
- } else if (!password || !confirm_password) {
- this.do_warn(_t("Login"), _t("Please enter a password and confirm it."));
- return false;
- } else if (password !== confirm_password) {
- this.do_warn(_t("Login"), _t("Passwords do not match; please retype them."));
+ var login_mode = this.get('login_mode');
+ if (login_mode === 'signup' || login_mode === 'reset') {
+ var params = this.get_params();
+ if (_.isEmpty(params)){
return false;
}
- var params = {
- dbname : db,
- token: this.params.token || "",
- name: name,
- login: login,
- password: password,
- };
-
var self = this,
super_ = this._super;
this.rpc('/auth_signup/signup', params)
@@ -156,21 +163,23 @@ openerp.auth_signup = function(instance) {
if (ev) {
ev.preventDefault();
}
+ var self = this;
var db = this.$("form [name=db]").val();
var login = this.$("form input[name=login]").val();
if (!db) {
this.do_warn(_t("Login"), _t("No database selected !"));
- return false;
+ return $.Deferred().reject();
} else if (!login) {
- this.do_warn(_t("Login"), _t("Please enter a username or email address."))
- return false;
+ this.do_warn(_t("Login"), _t("Please enter a username or email address."));
+ return $.Deferred().reject();
}
- var params = {
- dbname : db,
- login: login,
- };
- var url = "/auth_signup/reset_password?" + $.param(params);
- window.location = url;
+ return self.rpc("/auth_signup/reset_password", { dbname: db, login: login }).done(function(result) {
+ self.show_error(_t("An email has been sent with credentials to reset your password"));
+ self.set('login_mode', 'default');
+ }).fail(function(result, ev) {
+ ev.preventDefault();
+ self.show_error(result.message);
+ });
},
});
};
diff --git a/addons/auth_signup/static/src/xml/auth_signup.xml b/addons/auth_signup/static/src/xml/auth_signup.xml
index d1c04cb6807..9ef4f394e27 100644
--- a/addons/auth_signup/static/src/xml/auth_signup.xml
+++ b/addons/auth_signup/static/src/xml/auth_signup.xml
@@ -5,26 +5,32 @@
<t t-extend="Login">
<t t-jquery="form ul:first li:contains('Username')" t-operation="before">
- <li class="oe_signup_show">Name</li>
- <li class="oe_signup_show"><input name="name" type="text"/></li>
+ <li data-modes="signup reset">Name</li>
+ <li data-modes="signup reset"><input name="name" type="text"/></li>
</t>
<t t-jquery="form ul:first li:contains('Username')" t-operation="replace">
- <li class="oe_signup_hide">Username</li>
- <li class="oe_signup_show">Username (Email)</li>
+ <li data-modes="default">Username</li>
+ <li data-modes="signup reset">Username (Email)</li>
</t>
<t t-jquery="form ul:first li:has(input[name=password])" t-operation="after">
- <li class="oe_signup_show">Confirm Password</li>
- <li class="oe_signup_show"><input name="confirm_password" type="password"/></li>
+ <li data-modes="signup reset">Confirm Password</li>
+ <li data-modes="signup reset"><input name="confirm_password" type="password"/></li>
</t>
<t t-jquery="form ul:first li:has(button[name=submit])" t-operation="replace">
<li>
- <button class="oe_signup_hide" name="submit">Log in</button>
- <button class="oe_signup_show" name="submit">Sign up</button>
- <a class="oe_signup_hide oe_signup_signup" href="#">Sign Up</a>
- <a class="oe_signup_show oe_signup_back" href="#">Back to Login</a>
- <a class="oe_signup_reset_password" href="#">Reset password</a>
+ <button name="submit">
+ <span data-modes="default">Log in</span>
+ <span data-modes="signup">Sign Up</span>
+ <span data-modes="reset">Reset password</span>
+ </button>
+ <a class="oe_signup_signup" data-modes="default" href="#">Sign Up</a>
+ <a class="oe_signup_back" data-modes="signup reset" href="#">Back to Login</a>
+ <a class="oe_signup_reset_password" data-modes="default" href="#">Reset password</a>
</li>
</t>
+ <t t-jquery=".oe_login_manage_db">
+ this.attr('data-modes', 'default');
+ </t>
</t>
</templates>
diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py
index a3237c1620b..62c808ed556 100644
--- a/addons/base_action_rule/base_action_rule.py
+++ b/addons/base_action_rule/base_action_rule.py
@@ -214,6 +214,13 @@ class base_action_rule(osv.osv):
self._register_hook(cr, ids)
return True
+ def onchange_model_id(self, cr, uid, ids, model_id, context=None):
+ data = {'model': False, 'filter_pre_id': False, 'filter_id': False}
+ if model_id:
+ model = self.pool.get('ir.model').browse(cr, uid, model_id, context=context)
+ data.update({'model': model.model})
+ return {'value': data}
+
def _check(self, cr, uid, automatic=False, use_new_cursor=False, context=None):
""" This Function is called by scheduler. """
context = context or {}
diff --git a/addons/base_action_rule/base_action_rule_view.xml b/addons/base_action_rule/base_action_rule_view.xml
index 25ea4048bad..4e551fa21c5 100644
--- a/addons/base_action_rule/base_action_rule_view.xml
+++ b/addons/base_action_rule/base_action_rule_view.xml
@@ -15,7 +15,7 @@
<h1><field name="name"/></h1>
<group>
<group>
- <field name="model_id"/>
+ <field name="model_id" on_change="onchange_model_id(model_id, context)"/>
<field name="model" invisible="1"/>
</group>
<group>
diff --git a/addons/base_calendar/base_calendar_view.xml b/addons/base_calendar/base_calendar_view.xml
index e3ad980dae7..92ffad4be9a 100644
--- a/addons/base_calendar/base_calendar_view.xml
+++ b/addons/base_calendar/base_calendar_view.xml
@@ -70,7 +70,7 @@
<header>
<button name="do_confirm" string="Confirm" states="tentative,cancelled" type="object" class="oe_highlight"/>
<button name="do_tentative" states="confirmed,cancelled" string="Uncertain" type="object" class="oe_highlight"/>
- <button name="do_cancel" string="Cancel" states="tentative,confirmed" type="object"/>
+ <button name="do_cancel" string="Cancel Event" states="tentative,confirmed" type="object"/>
<field name="state" widget="statusbar"
statusbar_visible="tentative,confirmed" statusbar_colors='{"proforma":"blue"}'/>
</header>
diff --git a/addons/base_report_designer/base_report_designer.py b/addons/base_report_designer/base_report_designer.py
index 825d1d29c4c..180a534b07f 100644
--- a/addons/base_report_designer/base_report_designer.py
+++ b/addons/base_report_designer/base_report_designer.py
@@ -19,10 +19,11 @@
#
##############################################################################
+import base64
+import openerp.modules.registry
from openerp.osv import osv
from openerp_sxw2rml import sxw2rml
from StringIO import StringIO
-import base64
from openerp import pooler
from openerp import addons
@@ -55,7 +56,12 @@ class report_xml(osv.osv):
'report_sxw_content': base64.decodestring(file_sxw),
'report_rml_content': str(sxw2rml(sxwval, xsl=fp.read())),
})
+
+ # FIXME: this should be moved to an override of the ir.actions.report_xml.create() method
+ cr.commit()
pool.get('ir.actions.report.xml').register_all(cr)
+ openerp.modules.registry.RegistryManager.signal_registry_change(cr.dbname)
+
return True
def report_get(self, cr, uid, report_id, context=None):
diff --git a/addons/base_report_designer/plugin/openerp_report_designer.zip b/addons/base_report_designer/plugin/openerp_report_designer.zip
index 855fef319fb..7d03f45dbb4 100644
--- a/addons/base_report_designer/plugin/openerp_report_designer.zip
+++ b/addons/base_report_designer/plugin/openerp_report_designer.zip
Binary files differ
diff --git a/addons/base_report_designer/plugin/openerp_report_designer/bin/script/ExportToRML.py b/addons/base_report_designer/plugin/openerp_report_designer/bin/script/ExportToRML.py
index d92b2aec14f..d2605ce73e4 100644
--- a/addons/base_report_designer/plugin/openerp_report_designer/bin/script/ExportToRML.py
+++ b/addons/base_report_designer/plugin/openerp_report_designer/bin/script/ExportToRML.py
@@ -92,9 +92,9 @@ class ExportToRML( unohelper.Base, XJobExecutor ):
if docinfo.getUserFieldValue(2) == "":
ErrorDialog("Please Save this file on server","Use Send To Server Option in OpenERP Report Menu","Error")
exit(1)
- filename = self.GetAFileName()
- if not filename:
- exit(1)
+ filename = self.GetAFileName()
+ if not filename:
+ exit(1)
global passwd
self.password = passwd
try:
@@ -118,7 +118,7 @@ class ExportToRML( unohelper.Base, XJobExecutor ):
initPath = tempfile.gettempdir()
oUcb = createUnoService("com.sun.star.ucb.SimpleFileAccess")
if oUcb.exists(initPath):
- oFileDialog.setDisplayDirectory('file://' + ( os.name == 'nt' and '/' or '' ) + initPath )
+ oFileDialog.setDisplayDirectory('file://' + ( os.name == 'nt' and '/' or '' ) + initPath )
oFileDialog.setDefaultName(f_path )
diff --git a/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Fields.py b/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Fields.py
index 3e39b528b6b..90f85e57a0d 100644
--- a/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Fields.py
+++ b/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Fields.py
@@ -209,13 +209,13 @@ class Fields(unohelper.Base, XJobExecutor ):
key.sort()
myval=None
if not sVar.find("/")==-1:
- myval=sVar[:sVar.find("/")]
+ myval=sVar[:sVar.find("/")]
else:
myval=sVar
if myval in key:
if (res[myval]['type'] in ['many2one']):
sObject = res[myval]['relation']
- return self.getRes(sock,res[myval]['relation'], sVar[sVar.find("/")+1:])
+ return self.getRes(sock,res[myval]['relation'], sVar[sVar.find("/")+1:])
else:
return sObject
diff --git a/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Repeatln.py b/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Repeatln.py
index 16735d40f4e..f5e0972d2dd 100644
--- a/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Repeatln.py
+++ b/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Repeatln.py
@@ -166,33 +166,33 @@ class RepeatIn( unohelper.Base, XJobExecutor ):
self.sValue= "objects"
else:
sItem=""
- for anObject in self.aObjectList:
- if anObject[:anObject.find("(")] == sObject:
- sItem = anObject
- self.insVariable.setText( sItem )
-
- genTree(
- sItem[sItem.find("(")+1:sItem.find(")")],
- self.aListRepeatIn,
- self.insField,
- self.sMyHost,
- 2,
- ending=['one2many','many2many'],
- recur=['one2many','many2many']
- )
+ for anObject in self.aObjectList:
+ if anObject[:anObject.find("(")] == sObject:
+ sItem = anObject
+ self.insVariable.setText( sItem )
+
+ genTree(
+ sItem[sItem.find("(")+1:sItem.find(")")],
+ self.aListRepeatIn,
+ self.insField,
+ self.sMyHost,
+ 2,
+ ending=['one2many','many2many'],
+ recur=['one2many','many2many']
+ )
self.sValue= self.win.getListBoxItem("lstFields",self.aListRepeatIn.index(sFields))
for var in self.aVariableList:
- if var[:8] <> 'List of ':
- self.model_ids = self.sock.execute(database, uid, self.password, 'ir.model' , 'search', [('model','=',var[var.find("(")+1:var.find(")")])])
+ if var[:8] <> 'List of ':
+ self.model_ids = self.sock.execute(database, uid, self.password, 'ir.model' , 'search', [('model','=',var[var.find("(")+1:var.find(")")])])
else:
- self.model_ids = self.sock.execute(database, uid, self.password, 'ir.model' , 'search', [('model','=',var[8:])])
+ self.model_ids = self.sock.execute(database, uid, self.password, 'ir.model' , 'search', [('model','=',var[8:])])
fields=['name','model']
self.model_res = self.sock.execute(database, uid, self.password, 'ir.model', 'read', self.model_ids,fields)
if self.model_res <> []:
- if var[:8]<>'List of ':
+ if var[:8]<>'List of ':
self.insVariable.addItem(var[:var.find("(")+1] + self.model_res[0]['name'] + ")" ,self.insVariable.getItemCount())
else:
self.insVariable.addItem('List of ' + self.model_res[0]['name'] ,self.insVariable.getItemCount())
@@ -212,8 +212,8 @@ class RepeatIn( unohelper.Base, XJobExecutor ):
self.win.setEditText("txtName", self.sGVariable)
self.win.setEditText("txtUName",self.sGDisplayName)
else:
- self.win.setEditText("txtName",sMain[sMain.rfind("/")+1:])
- self.win.setEditText("txtUName","|-."+sItem[sItem.rfind("/")+1:]+".-|")
+ self.win.setEditText("txtName",sMain[sMain.rfind("/")+1:])
+ self.win.setEditText("txtUName","|-."+sItem[sItem.rfind("/")+1:]+".-|")
def cmbVariable_selected(self, oItemEvent):
@@ -225,15 +225,15 @@ class RepeatIn( unohelper.Base, XJobExecutor ):
self.win.removeListBoxItems("lstFields", 0, self.win.getListBoxItemCount("lstFields"))
sItem=self.win.getComboBoxText("cmbVariable")
for var in self.aVariableList:
- if var[:8]=='List of ':
- if var[:8]==sItem[:8]:
+ if var[:8]=='List of ':
+ if var[:8]==sItem[:8]:
sItem = var
- elif var[:var.find("(")+1] == sItem[:sItem.find("(")+1]:
+ elif var[:var.find("(")+1] == sItem[:sItem.find("(")+1]:
sItem = var
self.aListRepeatIn=[]
- data = ( sItem[sItem.rfind(" ") + 1:] == docinfo.getUserFieldValue(3) ) and docinfo.getUserFieldValue(3) or sItem[sItem.find("(")+1:sItem.find(")")]
- genTree( data, self.aListRepeatIn, self.insField, self.sMyHost, 2, ending=['one2many','many2many'], recur=['one2many','many2many'] )
+ data = ( sItem[sItem.rfind(" ") + 1:] == docinfo.getUserFieldValue(3) ) and docinfo.getUserFieldValue(3) or sItem[sItem.find("(")+1:sItem.find(")")]
+ genTree( data, self.aListRepeatIn, self.insField, self.sMyHost, 2, ending=['one2many','many2many'], recur=['one2many','many2many'] )
self.win.selectListBoxItemPos("lstFields", 0, True )
diff --git a/addons/base_report_designer/plugin/openerp_report_designer/bin/script/SendToServer.py b/addons/base_report_designer/plugin/openerp_report_designer/bin/script/SendToServer.py
index 86cf24edaa4..7e83910a427 100644
--- a/addons/base_report_designer/plugin/openerp_report_designer/bin/script/SendToServer.py
+++ b/addons/base_report_designer/plugin/openerp_report_designer/bin/script/SendToServer.py
@@ -123,6 +123,7 @@ class SendtoServer(unohelper.Base, XJobExecutor):
self.win.addFixedText("lblReportName", 2, 30, 50, 15, "Technical Name :")
self.win.addEdit("txtReportName", -5, 25, 123, 15,report_name)
self.win.addCheckBox("chkHeader", 51, 45, 70 ,15, "Corporate Header")
+ self.win.setCheckBoxState("chkHeader", True)
self.win.addFixedText("lblResourceType", 2 , 60, 50, 15, "Select Rpt. Type :")
self.win.addComboListBox("lstResourceType", -5, 58, 123, 15,True,itemListenerProc=self.lstbox_selected)
self.lstResourceType = self.win.getControl( "lstResourceType" )
@@ -190,7 +191,6 @@ class SendtoServer(unohelper.Base, XJobExecutor):
#sock = xmlrpclib.ServerProxy(docinfo.getUserFieldValue(0) +'/xmlrpc/object')
file_type = oDoc2.getURL()[7:].split(".")[-1]
- res = self.sock.execute(database, uid, self.password, 'ir.actions.report.xml', 'upload_report', int(docinfo.getUserFieldValue(2)),base64.encodestring(data),file_type,{})
params = {
'name': self.win.getEditText("txtName"),
'model': docinfo.getUserFieldValue(3),
@@ -200,7 +200,12 @@ class SendtoServer(unohelper.Base, XJobExecutor):
}
if self.win.getListBoxSelectedItem("lstResourceType")=='OpenOffice':
params['report_type']=file_type
- res = self.sock.execute(database, uid, self.password, 'ir.actions.report.xml', 'write', int(docinfo.getUserFieldValue(2)), params)
+ self.sock.execute(database, uid, self.password, 'ir.actions.report.xml', 'write', int(docinfo.getUserFieldValue(2)), params)
+
+ # Call upload_report as the *last* step, as it will call register_all() and cause the report service
+ # to be loaded - which requires all the data to be correct in the database
+ self.sock.execute(database, uid, self.password, 'ir.actions.report.xml', 'upload_report', int(docinfo.getUserFieldValue(2)),base64.encodestring(data),file_type,{})
+
self.logobj.log_write('SendToServer',LOG_INFO, ':Report %s successfully send using %s'%(params['name'],database))
self.win.endExecute()
else:
diff --git a/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Translation.py b/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Translation.py
index 13d02c86329..05f81a5c5b2 100644
--- a/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Translation.py
+++ b/addons/base_report_designer/plugin/openerp_report_designer/bin/script/Translation.py
@@ -112,43 +112,43 @@ class AddLang(unohelper.Base, XJobExecutor ):
text=cursor.getText()
tcur=text.createTextCursorByRange(cursor)
- self.aVariableList.extend( filter( lambda obj: obj[:obj.find("(")] == "Objects", self.aObjectList ) )
+ self.aVariableList.extend( filter( lambda obj: obj[:obj.find("(")] == "Objects", self.aObjectList ) )
for i in range(len(self.aItemList)):
- anItem = self.aItemList[i][1]
- component = self.aComponentAdd[i]
+ anItem = self.aItemList[i][1]
+ component = self.aComponentAdd[i]
if component == "Document":
- sLVal = anItem[anItem.find(",'") + 2:anItem.find("')")]
- self.aVariableList.extend( filter( lambda obj: obj[:obj.find("(")] == sLVal, self.aObjectList ) )
+ sLVal = anItem[anItem.find(",'") + 2:anItem.find("')")]
+ self.aVariableList.extend( filter( lambda obj: obj[:obj.find("(")] == sLVal, self.aObjectList ) )
if tcur.TextSection:
getRecersiveSection(tcur.TextSection,self.aSectionList)
if component in self.aSectionList:
- sLVal = anItem[anItem.find(",'") + 2:anItem.find("')")]
- self.aVariableList.extend( filter( lambda obj: obj[:obj.find("(")] == sLVal, self.aObjectList ) )
+ sLVal = anItem[anItem.find(",'") + 2:anItem.find("')")]
+ self.aVariableList.extend( filter( lambda obj: obj[:obj.find("(")] == sLVal, self.aObjectList ) )
if tcur.TextTable:
- if not component == "Document" and component[component.rfind(".") + 1:] == tcur.TextTable.Name:
+ if not component == "Document" and component[component.rfind(".") + 1:] == tcur.TextTable.Name:
VariableScope(tcur,self.insVariable,self.aObjectList,self.aComponentAdd,self.aItemList,component)
self.bModify=bFromModify
if self.bModify==True:
sItem=""
- for anObject in self.aObjectList:
- if anObject[:anObject.find("(")] == sVariable:
- sItem = anObject
- self.insVariable.setText( sItem )
- genTree(sItem[sItem.find("(")+1:sItem.find(")")],self.aListFields, self.insField,self.sMyHost,2,ending_excl=['one2many','many2one','many2many','reference'], recur=['many2one'])
+ for anObject in self.aObjectList:
+ if anObject[:anObject.find("(")] == sVariable:
+ sItem = anObject
+ self.insVariable.setText( sItem )
+ genTree(sItem[sItem.find("(")+1:sItem.find(")")],self.aListFields, self.insField,self.sMyHost,2,ending_excl=['one2many','many2one','many2many','reference'], recur=['many2one'])
self.sValue= self.win.getListBoxItem("lstFields",self.aListFields.index(sFields))
for var in self.aVariableList:
- self.model_ids = self.sock.execute(database, uid, self.password, 'ir.model' , 'search', [('model','=',var[var.find("(")+1:var.find(")")])])
+ self.model_ids = self.sock.execute(database, uid, self.password, 'ir.model' , 'search', [('model','=',var[var.find("(")+1:var.find(")")])])
fields=['name','model']
self.model_res = self.sock.execute(database, uid, self.password, 'ir.model', 'read', self.model_ids,fields)
if self.model_res <> []:
- self.insVariable.addItem(var[:var.find("(")+1] + self.model_res[0]['name'] + ")" ,self.insVariable.getItemCount())
+ self.insVariable.addItem(var[:var.find("(")+1] + self.model_res[0]['name'] + ")" ,self.insVariable.getItemCount())
else:
self.insVariable.addItem(var ,self.insVariable.getItemCount())
@@ -165,15 +165,15 @@ class AddLang(unohelper.Base, XJobExecutor ):
docinfo=doc.getDocumentInfo()
sItem= self.win.getComboBoxText("cmbVariable")
for var in self.aVariableList:
- if var[:var.find("(")+1]==sItem[:sItem.find("(")+1]:
+ if var[:var.find("(")+1]==sItem[:sItem.find("(")+1]:
sItem = var
sMain=self.aListFields[self.win.getListBoxSelectedItemPos("lstFields")]
t=sMain.rfind('/lang')
if t!=-1:
- sObject=self.getRes(self.sock,sItem[sItem.find("(")+1:-1],sMain[1:])
+ sObject=self.getRes(self.sock,sItem[sItem.find("(")+1:-1],sMain[1:])
ids = self.sock.execute(database, uid, self.password, sObject , 'search', [])
res = self.sock.execute(database, uid, self.password, sObject , 'read',[ids[0]])
- self.win.setEditText("txtUName",res[0][sMain[sMain.rfind("/")+1:]])
+ self.win.setEditText("txtUName",res[0][sMain[sMain.rfind("/")+1:]])
else:
ErrorDialog("Please select a language.")
@@ -192,13 +192,13 @@ class AddLang(unohelper.Base, XJobExecutor ):
key.sort()
myval=None
if not sVar.find("/")==-1:
- myval=sVar[:sVar.find("/")]
+ myval=sVar[:sVar.find("/")]
else:
myval=sVar
if myval in key:
if (res[myval]['type'] in ['many2one']):
sObject = res[myval]['relation']
- return self.getRes(sock,res[myval]['relation'], sVar[sVar.find("/")+1:])
+ return self.getRes(sock,res[myval]['relation'], sVar[sVar.find("/")+1:])
else:
return sObject
@@ -213,18 +213,18 @@ class AddLang(unohelper.Base, XJobExecutor ):
self.aListFields=[]
tempItem = self.win.getComboBoxText("cmbVariable")
for var in self.aVariableList:
- if var[:var.find("(")] == tempItem[:tempItem.find("(")]:
+ if var[:var.find("(")] == tempItem[:tempItem.find("(")]:
sItem=var
- genTree(
- sItem[ sItem.find("(") + 1:sItem.find(")")],
- self.aListFields,
- self.insField,
- self.sMyHost,
- 2,
- ending_excl=['one2many','many2one','many2many','reference'],
- recur=['many2one']
- )
+ genTree(
+ sItem[ sItem.find("(") + 1:sItem.find(")")],
+ self.aListFields,
+ self.insField,
+ self.sMyHost,
+ 2,
+ ending_excl=['one2many','many2one','many2many','reference'],
+ recur=['many2one']
+ )
except:
import traceback;traceback.print_exc()
diff --git a/addons/base_report_designer/plugin/openerp_report_designer/bin/script/lib/functions.py b/addons/base_report_designer/plugin/openerp_report_designer/bin/script/lib/functions.py
index 1b614224147..1d10f0f6a4c 100644
--- a/addons/base_report_designer/plugin/openerp_report_designer/bin/script/lib/functions.py
+++ b/addons/base_report_designer/plugin/openerp_report_designer/bin/script/lib/functions.py
@@ -87,19 +87,19 @@ def genTree(object, aList, insField, host, level=3, ending=None, ending_excl=Non
def VariableScope(oTcur, insVariable, aObjectList, aComponentAdd, aItemList, sTableName=""):
if sTableName.find(".") != -1:
- for i in range(len(aItemList)):
+ for i in range(len(aItemList)):
if aComponentAdd[i]==sTableName:
- sLVal=aItemList[i][1][aItemList[i][1].find(",'")+2:aItemList[i][1].find("')")]
+ sLVal=aItemList[i][1][aItemList[i][1].find(",'")+2:aItemList[i][1].find("')")]
for j in range(len(aObjectList)):
- if aObjectList[j][:aObjectList[j].find("(")] == sLVal:
+ if aObjectList[j][:aObjectList[j].find("(")] == sLVal:
insVariable.append(aObjectList[j])
- VariableScope(oTcur,insVariable,aObjectList,aComponentAdd,aItemList, sTableName[:sTableName.rfind(".")])
+ VariableScope(oTcur,insVariable,aObjectList,aComponentAdd,aItemList, sTableName[:sTableName.rfind(".")])
else:
- for i in range(len(aItemList)):
+ for i in range(len(aItemList)):
if aComponentAdd[i]==sTableName:
- sLVal=aItemList[i][1][aItemList[i][1].find(",'")+2:aItemList[i][1].find("')")]
+ sLVal=aItemList[i][1][aItemList[i][1].find(",'")+2:aItemList[i][1].find("')")]
for j in range(len(aObjectList)):
- if aObjectList[j][:aObjectList[j].find("(")] == sLVal and sLVal!="":
+ if aObjectList[j][:aObjectList[j].find("(")] == sLVal and sLVal!="":
insVariable.append(aObjectList[j])
def getList(aObjectList, host, count):
@@ -145,8 +145,8 @@ def getRelation(sRelName, sItem, sObjName, aObjectList, host):
if k == sItem:
aObjectList.append(sObjName + "(" + res[k]['relation'] + ")")
return 0
- if k == sItem[:sItem.find(".")]:
- getRelation(res[k]['relation'], sItem[sItem.find(".")+1:], sObjName,aObjectList,host)
+ if k == sItem[:sItem.find(".")]:
+ getRelation(res[k]['relation'], sItem[sItem.find(".")+1:], sObjName,aObjectList,host)
def getPath(sPath, sMain):
@@ -157,13 +157,13 @@ def getPath(sPath, sMain):
oPar = oParEnum.nextElement()
if oPar.supportsService("com.sun.star.text.TextField.DropDown"):
sItem=oPar.Items[1]
- if sPath[:sPath.find(".")] == sMain:
+ if sPath[:sPath.find(".")] == sMain:
break;
else:
res = re.findall('\\[\\[ *([a-zA-Z0-9_\.]+) *\\]\\]',sPath)
if len(res) <> 0:
- if sItem[sItem.find(",'")+2:sItem.find("')")] == sPath[:sPath.find(".")]:
- sPath = sItem[sItem.find("(")+1:sItem.find(",")] + sPath[sPath.find("."):]
+ if sItem[sItem.find(",'")+2:sItem.find("')")] == sPath[:sPath.find(".")]:
+ sPath = sItem[sItem.find("(")+1:sItem.find(",")] + sPath[sPath.find("."):]
getPath(sPath, sMain)
return sPath
diff --git a/addons/base_report_designer/plugin/openerp_report_designer/bin/script/lib/tools.py b/addons/base_report_designer/plugin/openerp_report_designer/bin/script/lib/tools.py
index 53dc81f5bc3..8e03d853773 100644
--- a/addons/base_report_designer/plugin/openerp_report_designer/bin/script/lib/tools.py
+++ b/addons/base_report_designer/plugin/openerp_report_designer/bin/script/lib/tools.py
@@ -22,21 +22,21 @@
import urllib
def get_absolute_file_path(url):
- url_unquoted = urllib.unquote(url)
- return os.name == 'nt' and url_unquoted[1:] or url_unquoted
+ url_unquoted = urllib.unquote(url)
+ return os.name == 'nt' and url_unquoted[1:] or url_unquoted
# This function reads the content of a file and return it to the caller
def read_data_from_file(filename):
- fp = file( filename, "rb" )
- data = fp.read()
- fp.close()
- return data
+ fp = file( filename, "rb" )
+ data = fp.read()
+ fp.close()
+ return data
# This function writes the content to a file
def write_data_to_file(filename, data):
- fp = file( filename, 'wb' )
- fp.write( data )
- fp.close()
+ fp = file( filename, 'wb' )
+ fp.write( data )
+ fp.close()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/base_report_designer/plugin/openerp_report_designer/bin/script/modify.py b/addons/base_report_designer/plugin/openerp_report_designer/bin/script/modify.py
index b5f5a1501b9..60edbc71d96 100644
--- a/addons/base_report_designer/plugin/openerp_report_designer/bin/script/modify.py
+++ b/addons/base_report_designer/plugin/openerp_report_designer/bin/script/modify.py
@@ -75,42 +75,42 @@ class modify(unohelper.Base, XJobExecutor ):
self.sMyHost= docinfo.getUserFieldValue(0)
else:
ErrorDialog(
- "Please insert user define field Field-1",
- "Just go to File->Properties->User Define \n"
- "Field-1 E.g. http://localhost:8069"
- )
+ "Please insert user define field Field-1",
+ "Just go to File->Properties->User Define \n"
+ "Field-1 E.g. http://localhost:8069"
+ )
exit(1)
# Check weather Field-4 is available or not otherwise exit from application
if not docinfo.getUserFieldValue(3) == "" and not docinfo.getUserFieldValue(0)=="":
if self.oVC.TextField:
self.oCurObj=self.oVC.TextField
- item = self.oCurObj.Items[0]
+ item = self.oCurObj.Items[0]
- kind, group1, group2 = self.getOperation(self.oCurObj.Items[1] )
+ kind, group1, group2 = self.getOperation(self.oCurObj.Items[1] )
- start_group1 = group1[:group1.find(".")]
- stop_group1 = group1[group1.find("."):].replace(".", "/")
+ start_group1 = group1[:group1.find(".")]
+ stop_group1 = group1[group1.find("."):].replace(".", "/")
if kind == "field":
- Fields( start_group1, stop_group1, item, True )
+ Fields( start_group1, stop_group1, item, True )
elif kind == "expression":
Expression( group1, item, True )
elif kind == "repeatIn":
- RepeatIn( start_group1, group2, stop_group1, item, True )
+ RepeatIn( start_group1, group2, stop_group1, item, True )
else:
ErrorDialog(
"Please place your cursor at beginning of field that you want to modify.",""
- )
+ )
else:
ErrorDialog(
- "Please insert user define field Field-1 or Field-4",
- "Just go to File->Properties->User Define \n"
- "Field-1 E.g. http://localhost:8069 \n"
- "OR \n"
- "Field-4 E.g. account.invoice"
- )
+ "Please insert user define field Field-1 or Field-4",
+ "Just go to File->Properties->User Define \n"
+ "Field-1 E.g. http://localhost:8069 \n"
+ "OR \n"
+ "Field-4 E.g. account.invoice"
+ )
exit(1)
def getOperation(self, str):
@@ -121,14 +121,14 @@ class modify(unohelper.Base, XJobExecutor ):
method2 = lambda x: (u'field', x.group(1), None)
method3 = lambda x: (u'expression', x.group(1), None)
regexes = [
- ('\\[\\[ *repeatIn\\( *(.+)*, *\'([a-zA-Z0-9_]+)\' *\\) *\\]\\]', method1),
- ('\\[\\[ *([a-zA-Z0-9_\.]+) *\\]\\]', method2),
- ('\\[\\[ *(.+) *\\]\\]', method3)
+ ('\\[\\[ *repeatIn\\( *(.+)*, *\'([a-zA-Z0-9_]+)\' *\\) *\\]\\]', method1),
+ ('\\[\\[ *([a-zA-Z0-9_\.]+) *\\]\\]', method2),
+ ('\\[\\[ *(.+) *\\]\\]', method3)
]
for (rule,method) in regexes:
- res = re.match(rule, str)
- if res:
- return method(res)
+ res = re.match(rule, str)
+ if res:
+ return method(res)
if __name__<>"package":
modify(None)
diff --git a/addons/base_report_designer/plugin/openerp_report_designer/test/test_fields.py b/addons/base_report_designer/plugin/openerp_report_designer/test/test_fields.py
index c23e0e739bf..948b3733b70 100644
--- a/addons/base_report_designer/plugin/openerp_report_designer/test/test_fields.py
+++ b/addons/base_report_designer/plugin/openerp_report_designer/test/test_fields.py
@@ -11,23 +11,23 @@ import time
sock = xmlrpclib.ServerProxy('http://localhost:8069/xmlrpc/object')
def get(object, level=3, ending=None, ending_excl=None, recur=None, root=''):
- if ending is None:
- ending = []
- if ending_excl is None:
- ending_excl = []
- if recur is None:
- recur = []
- res = sock.execute('terp', 3, 'admin', 'account.invoice', 'fields_get')
- key = res.keys()
- key.sort()
- for k in key:
- if (not ending or res[k]['type'] in ending) and ((not ending_excl) or not (res[k]['type'] in ending_excl)):
- print root+'/'+k
-
- if res[k]['type'] in recur:
- print root+'/'+k
- if (res[k]['type'] in recur) and (level>0):
- get(res[k]['relation'], level-1, ending, ending_excl, recur, root+'/'+k)
+ if ending is None:
+ ending = []
+ if ending_excl is None:
+ ending_excl = []
+ if recur is None:
+ recur = []
+ res = sock.execute('terp', 3, 'admin', 'account.invoice', 'fields_get')
+ key = res.keys()
+ key.sort()
+ for k in key:
+ if (not ending or res[k]['type'] in ending) and ((not ending_excl) or not (res[k]['type'] in ending_excl)):
+ print root+'/'+k
+
+ if res[k]['type'] in recur:
+ print root+'/'+k
+ if (res[k]['type'] in recur) and (level>0):
+ get(res[k]['relation'], level-1, ending, ending_excl, recur, root+'/'+k)
print 'Field selection for a rields', '='*40
get('account.invoice', level=0, ending_excl=['one2many','many2one','many2many','reference'], recur=['many2one'])
diff --git a/addons/board/static/src/js/dashboard.js b/addons/board/static/src/js/dashboard.js
index 0a38c744738..bd9625a4fa8 100644
--- a/addons/board/static/src/js/dashboard.js
+++ b/addons/board/static/src/js/dashboard.js
@@ -177,8 +177,10 @@ instance.web.form.DashBoard = instance.web.form.FormWidget.extend({
view_mode = action_attrs.view_mode;
// evaluate action_attrs context and domain
+ action_attrs.context_string = action_attrs.context;
action_attrs.context = instance.web.pyeval.eval(
'context', action_attrs.context || {});
+ action_attrs.domain_string = action_attrs.domain;
action_attrs.domain = instance.web.pyeval.eval(
'domain', action_attrs.domain || [], action_attrs.context);
if (action_attrs.context['dashboard_merge_domains_contexts'] === false) {
@@ -301,9 +303,9 @@ instance.web.form.DashBoard = instance.web.form.FormWidget.extend({
instance.web.form.DashBoardLegacy = instance.web.form.DashBoard.extend({
renderElement: function() {
if (this.node.tag == 'hpaned') {
- this.node.attrs.style = '2-1';
+ this.node.attrs.layout = '2-1';
} else if (this.node.tag == 'vpaned') {
- this.node.attrs.style = '1';
+ this.node.attrs.layout = '1';
}
this.node.tag = 'board';
_.each(this.node.children, function(child) {
diff --git a/addons/board/static/src/xml/board.xml b/addons/board/static/src/xml/board.xml
index 03ede793036..9e1f15562a3 100644
--- a/addons/board/static/src/xml/board.xml
+++ b/addons/board/static/src/xml/board.xml
@@ -10,7 +10,7 @@
<span> Change Layout </span>
</button>
</div>
- <table t-att-data-layout="node.attrs.style" t-attf-class="oe_dashboard oe_dashboard_layout_#{node.attrs.style}" cellspacing="0" cellpadding="0" border="0">
+ <table t-att-data-layout="node.attrs.layout" t-attf-class="oe_dashboard oe_dashboard_layout_#{node.attrs.layout}" cellspacing="0" cellpadding="0" border="0">
<tr>
<td t-foreach="node.children" t-as="column" t-if="column.tag == 'column'"
t-att-id="view.element_id + '_column_' + column_index" t-attf-class="oe_dashboard_column index_#{column_index}">
diff --git a/addons/crm/crm_data.xml b/addons/crm/crm_data.xml
index 3726c744b2e..613141b5818 100644
--- a/addons/crm/crm_data.xml
+++ b/addons/crm/crm_data.xml
@@ -80,8 +80,7 @@
basis for email recipients, name and to ease the definition of a further elaborated template. -->
<record id="email_template_opportunity_mail" model="email.template">
<field name="name">Opportunity - Send Emails</field>
- <field name="email_from">${object.user_id.email or ''}</field>
- <field name="subject">Opportunity ${object.name | h})</field>
+ <field name="subject">${object.name}</field>
<field name="model_id" ref="crm.model_crm_lead"/>
<field name="auto_delete" eval="True"/>
<field name="email_recipients">${object.partner_id.id}</field>
diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py
index f4b27fa4d9d..6c74ead0140 100644
--- a/addons/crm/crm_lead.py
+++ b/addons/crm/crm_lead.py
@@ -259,7 +259,9 @@ class crm_lead(base_stage, format_address, osv.osv):
'channel_id': fields.many2one('crm.case.channel', 'Channel', help="Communication channel (mail, direct, phone, ...)"),
'contact_name': fields.char('Contact Name', size=64),
'partner_name': fields.char("Customer Name", size=64,help='The name of the future partner company that will be created while converting the lead into opportunity', select=1),
- 'opt_out': fields.boolean('Opt-Out', oldname='optout', help="If opt-out is checked, this contact has refused to receive emails or unsubscribed to a campaign."),
+ 'opt_out': fields.boolean('Opt-Out', oldname='optout',
+ help="If opt-out is checked, this contact has refused to receive emails for mass mailing and marketing campaign. "
+ "Filter 'Available for Mass Mailing' allows users to filter the leads when performing mass mailing."),
'type':fields.selection([ ('lead','Lead'), ('opportunity','Opportunity'), ],'Type', help="Type is used to separate Leads and Opportunities"),
'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority', select=True),
'date_closed': fields.datetime('Closed', readonly=True),
@@ -711,7 +713,7 @@ class crm_lead(base_stage, format_address, osv.osv):
'parent_id': parent_id,
'phone': lead.phone,
'mobile': lead.mobile,
- 'email': lead.email_from and tools.email_split(lead.email_from)[0],
+ 'email': tools.email_split(lead.email_from) and tools.email_split(lead.email_from)[0] or False,
'fax': lead.fax,
'title': lead.title and lead.title.id or False,
'function': lead.function,
@@ -930,7 +932,7 @@ class crm_lead(base_stage, format_address, osv.osv):
try:
compose_form_id = ir_model_data.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')[1]
except ValueError:
- compose_form_id = False
+ compose_form_id = False
if context is None:
context = {}
ctx = context.copy()
@@ -961,13 +963,22 @@ class crm_lead(base_stage, format_address, osv.osv):
return [lead.section_id.message_get_reply_to()[0] if lead.section_id else False
for lead in self.browse(cr, uid, ids, context=context)]
+ def message_get_suggested_recipients(self, cr, uid, ids, context=None):
+ recipients = super(crm_lead, self).message_get_suggested_recipients(cr, uid, ids, context=context)
+ for lead in self.browse(cr, uid, ids, context=context):
+ if lead.partner_id:
+ self._message_add_suggested_recipient(cr, uid, recipients, lead, partner=lead.partner_id, reason=_('Customer'))
+ elif lead.email_from:
+ self._message_add_suggested_recipient(cr, uid, recipients, lead, email=lead.email_from, reason=_('Customer Email'))
+ return recipients
+
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 = {}
-
+ if custom_values is None:
+ custom_values = {}
desc = html2plaintext(msg.get('body')) if msg.get('body') else ''
defaults = {
'name': msg.get('subject') or _("No Subject"),
@@ -1015,9 +1026,12 @@ class crm_lead(base_stage, format_address, osv.osv):
def schedule_phonecall_send_note(self, cr, uid, ids, phonecall_id, action, context=None):
phonecall = self.pool.get('crm.phonecall').browse(cr, uid, [phonecall_id], context=context)[0]
- if action == 'log': prefix = 'Logged'
- else: prefix = 'Scheduled'
- message = _("<b>%s a call</b> for the <em>%s</em>.") % (prefix, phonecall.date)
+ if action == 'log':
+ prefix = 'Logged'
+ else:
+ prefix = 'Scheduled'
+ suffix = ' %s' % phonecall.description
+ message = _("%s a call for %s.%s") % (prefix, phonecall.date, suffix)
return self.message_post(cr, uid, ids, body=message, context=context)
def onchange_state(self, cr, uid, ids, state_id, context=None):
diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml
index c257faa001e..494e9926279 100644
--- a/addons/crm/crm_lead_view.xml
+++ b/addons/crm/crm_lead_view.xml
@@ -97,7 +97,7 @@
states="draft,open,pending" help="Convert to Opportunity" class="oe_highlight"/>
<button name="case_reset" string="Reset" type="object"
states="cancel"/>
- <button name="case_cancel" string="Cancel" type="object"
+ <button name="case_cancel" string="Cancel Case" type="object"
states="draft,open,pending"/>
<field name="stage_id" widget="statusbar" clickable="True"
on_change="onchange_stage_id(stage_id)"/>
@@ -164,7 +164,9 @@
<field name="priority"/>
<field name="categ_ids"
widget="many2many_tags"
- domain="[('object_id.model','=','crm.lead')]"/>
+ domain="[('object_id.model','=','crm.lead')]"
+ context="{'object_name': 'crm.lead'}"
+ />
</group>
</group>
<notebook colspan="4">
@@ -340,18 +342,21 @@
<filter string="Assigned to My Team(s)"
domain="[('section_id.member_ids', 'in', [uid])]" context="{'invisible_section': False}"
help="Leads that are assigned to any sales teams I am member of"/>
- <separator/>
+ <separator />
+ <filter string="Available for mass mailing"
+ name='not_opt_out' domain="[('opt_out', '=', False)]"
+ help="Leads that did not ask not to be included in mass mailing campaigns"/>
+ <separator />
<group expand="0" string="Group By...">
<filter string="Salesperson" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Team" domain="[]" context="{'group_by':'section_id'}"/>
<filter string="Stage" domain="[]" context="{'group_by':'stage_id'}"/>
<filter string="Customer" help="Partner" domain="[]" context="{'group_by':'partner_id'}"/>
<filter string="Country" domain="[]" context="{'group_by':'country_id'}"/>
- <filter string="Stage" domain="[]" context="{'group_by':'stage_id'}"/>
<filter string="Referrer" domain="[]" context="{'group_by':'referred'}"/>
<filter string="Campaign" domain="[]" context="{'group_by':'type_id'}"/>
<filter string="Channel" domain="[]" context="{'group_by':'channel_id'}"/>
- <filter string="Creation" domain="[]" context="{'group_by':'create_date'}" groups="base.group_no_one"/>
+ <filter string="Creation" domain="[]" context="{'group_by':'create_date'}"/>
</group>
<group string="Display">
<filter string="Show Countries" context="{'invisible_country': False}" help="Show Countries"/>
@@ -378,7 +383,6 @@
states="draft,open,pending" class="oe_highlight"/>
<button name="case_mark_lost" string="Mark Lost" type="object"
states="draft,open" class="oe_highlight"/>
- <button name="new_mail_send" string="Send Mail" type="object" states="draft,open,pending"/>
<field name="stage_id" widget="statusbar" clickable="True"/>
</header>
<sheet>
@@ -568,7 +572,7 @@
<filter string="Referrer" domain="[]" context="{'group_by':'referred'}"/>
<filter string="Campaign" domain="[]" context="{'group_by':'type_id'}"/>
<filter string="Channel" domain="[]" context="{'group_by':'channel_id'}"/>
- <filter string="Creation" domain="[]" context="{'group_by':'create_date'}" groups="base.group_no_one"/>
+ <filter string="Creation" domain="[]" context="{'group_by':'create_date'}"/>
</group>
<group string="Display">
<filter string="Show Sales Team" context="{'invisible_section': False}" domain="[]" help="Show Sales Team"/>
diff --git a/addons/crm/crm_phonecall_view.xml b/addons/crm/crm_phonecall_view.xml
index ccee49ba17b..67df85e6178 100644
--- a/addons/crm/crm_phonecall_view.xml
+++ b/addons/crm/crm_phonecall_view.xml
@@ -73,7 +73,7 @@
states="open,pending"/>
<button name="case_reset" string="Reset to Todo" type="object"
states="cancel"/>
- <button name="case_cancel" string="Cancel" type="object"
+ <button name="case_cancel" string="Cancel Call" type="object"
states="draft,open,pending"/>
<field name="state" widget="statusbar" nolabel="1" statusbar_visible="open,done"/>
</header>
diff --git a/addons/crm/wizard/crm_opportunity_to_phonecall.py b/addons/crm/wizard/crm_opportunity_to_phonecall.py
index ad9d30c80a4..ee7ed1fc31f 100644
--- a/addons/crm/wizard/crm_opportunity_to_phonecall.py
+++ b/addons/crm/wizard/crm_opportunity_to_phonecall.py
@@ -52,8 +52,6 @@ class crm_opportunity2phonecall(osv.osv_memory):
res.update({'categ_id': categ_id})
if 'partner_id' in fields:
res.update({'partner_id': opp.partner_id and opp.partner_id.id or False})
- if 'note' in fields:
- res.update({'note': opp.description})
if 'contact_name' in fields:
res.update({'contact_name': opp.partner_id and opp.partner_id.name or False})
if 'phone' in fields:
diff --git a/addons/crm/wizard/crm_opportunity_to_phonecall_view.xml b/addons/crm/wizard/crm_opportunity_to_phonecall_view.xml
index 6e44573cc89..ad369448517 100644
--- a/addons/crm/wizard/crm_opportunity_to_phonecall_view.xml
+++ b/addons/crm/wizard/crm_opportunity_to_phonecall_view.xml
@@ -13,14 +13,14 @@
<group>
<field name="action"/>
<field name="name"/>
+ <field name="categ_id" string="Type"
+ widget="selection"
+ domain="[('object_id.model', '=', 'crm.phonecall')]"
+ groups="base.group_no_one"/>
<field name="date" string="Planned Date" attrs="{'invisible': [('action','=','log')]}"/>
</group>
<group>
<field name="partner_id" readonly="True"/>
- <field name="categ_id" string="Type"
- widget="selection"
- domain="[('object_id.model', '=', 'crm.phonecall')]"
- groups="base.group_no_one"/>
<field name="phone"/>
<field name="user_id" attrs="{'invisible': [('action','=','log')]}"/>
<field name="section_id" widget="selection" attrs="{'invisible': [('action','=','log')]}"/>
diff --git a/addons/crm_claim/crm_claim_view.xml b/addons/crm_claim/crm_claim_view.xml
index 8b6217b97e1..b1ab72e20f6 100644
--- a/addons/crm_claim/crm_claim_view.xml
+++ b/addons/crm_claim/crm_claim_view.xml
@@ -168,7 +168,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
- <field name="message_ids" widget="mail_thread" placeholder="Share a note..."/>
+ <field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
diff --git a/addons/crm_helpdesk/crm_helpdesk_view.xml b/addons/crm_helpdesk/crm_helpdesk_view.xml
index 1640b9d0318..6c0e3433afe 100644
--- a/addons/crm_helpdesk/crm_helpdesk_view.xml
+++ b/addons/crm_helpdesk/crm_helpdesk_view.xml
@@ -31,8 +31,8 @@
<header>
<button name="case_open" string="Open" type="object" class="oe_highlight"
states="draft,pending"/>
- <button name="case_close" string="Close" type="object" states="draft,pending"/>
- <button name="case_close" string="Close" type="object" states="open" class="oe_highlight"/>
+ <button name="case_close" string="Close Case" type="object" states="draft,pending"/>
+ <button name="case_close" string="Close Case" type="object" states="open" class="oe_highlight"/>
<button name="case_pending" string="Pending" type="object"
states="draft"/>
<button name="case_pending" string="Pending" type="object"
@@ -41,7 +41,7 @@
states="cancel,done"/>
<button name="case_escalate" string="Escalate" type="object"
states="open,draft,pending"/>
- <button name="case_cancel" string="Cancel" type="object"
+ <button name="case_cancel" string="Cancel Case" type="object"
states="draft,open,pending"/>
<field name="state" nolabel="1" widget="statusbar" statusbar_visible="draft,open,done" statusbar_colors='{"pending":"blue"}'/>
</header>
@@ -95,7 +95,7 @@
</notebook>
</sheet>
<div class="oe_chatter">
- <field name="message_ids" widget="mail_thread" placeholder="Share a note..."/>
+ <field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
</div>
</form>
diff --git a/addons/crm_partner_assign/__init__.py b/addons/crm_partner_assign/__init__.py
index 8d4bf993aff..bb4a3bb53be 100644
--- a/addons/crm_partner_assign/__init__.py
+++ b/addons/crm_partner_assign/__init__.py
@@ -19,7 +19,7 @@
#
##############################################################################
-import partner_geo_assign
+import crm_partner_assign
import wizard
import report
diff --git a/addons/crm_partner_assign/__openerp__.py b/addons/crm_partner_assign/__openerp__.py
index cb952606e76..b9b21ca7a6a 100644
--- a/addons/crm_partner_assign/__openerp__.py
+++ b/addons/crm_partner_assign/__openerp__.py
@@ -44,6 +44,7 @@ You can also use the geolocalization without using the GPS coordinates.
'res_partner_view.xml',
'wizard/crm_forward_to_partner_view.xml',
'crm_lead_view.xml',
+ 'crm_partner_assign_data.xml',
'report/crm_lead_report_view.xml',
'report/crm_partner_report_view.xml',
],
diff --git a/addons/crm_partner_assign/partner_geo_assign.py b/addons/crm_partner_assign/crm_partner_assign.py
index 80d100ceb8c..b13a75a2591 100644
--- a/addons/crm_partner_assign/partner_geo_assign.py
+++ b/addons/crm_partner_assign/crm_partner_assign.py
@@ -71,7 +71,6 @@ class res_partner_grade(osv.osv):
_defaults = {
'active': lambda *args: 1
}
-res_partner_grade()
class res_partner_activation(osv.osv):
_name = 'res.partner.activation'
@@ -82,7 +81,6 @@ class res_partner_activation(osv.osv):
'name' : fields.char('Name', size=32, required=True),
}
-res_partner_activation()
class res_partner(osv.osv):
_inherit = "res.partner"
@@ -120,7 +118,6 @@ class res_partner(osv.osv):
'date_localization': fields.date.context_today(self,cr,uid,context=context)
}, context=context)
return True
-res_partner()
class crm_lead(osv.osv):
_inherit = "crm.lead"
@@ -164,8 +161,9 @@ class crm_lead(osv.osv):
self.assign_geo_localize(cr, uid, [lead.id], lead.partner_latitude, lead.partner_longitude, context=context)
partner = res_partner.browse(cr, uid, partner_id, context=context)
if partner.user_id:
+ salesteam_id = partner.section_id and partner.section_id.id or False
for lead_id in ids:
- self.allocate_salesman(cr, uid, [lead_id], [partner.user_id.id], context=context)
+ self.allocate_salesman(cr, uid, [lead_id], [partner.user_id.id], team_id=salesteam_id, context=context)
self.write(cr, uid, [lead.id], {'date_assign': fields.date.context_today(self,cr,uid,context=context), 'partner_assigned_id': partner_id}, context=context)
return res
@@ -260,7 +258,7 @@ class crm_lead(osv.osv):
res_partner_ids[lead.id] = partner_id
break
return res_partner_ids
-crm_lead()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
diff --git a/addons/crm_partner_assign/crm_partner_assign_data.xml b/addons/crm_partner_assign/crm_partner_assign_data.xml
new file mode 100644
index 00000000000..dab50192641
--- /dev/null
+++ b/addons/crm_partner_assign/crm_partner_assign_data.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding='UTF-8'?>
+<openerp>
+ <data>
+ <record id="crm_partner_assign_email_template" model="email.template">
+ <field name="name">Lead forward</field>
+ <field name="email_from"></field>
+ <field name="subject">Fwd: Lead: ${object.name}</field>
+ <field name="email_to"></field>
+ <field name="lang"></field>
+ <field name="model_id" ref="crm.model_crm_lead"/>
+ <field name="auto_delete" eval="True"/>
+ <field name="body_html"><![CDATA[
+Hello ${object.partner_assigned_id.name},
+<p>
+Here is a lead that might interest you.
+</p>
+<p>
+Please keep me informed about your actions about it so that I can keep an
+accurate follow-up of it and help you in the sale cycle.
+</p>
+<p>
+Your account manager,<br/>
+${object.user_id.name},<br/>
+${object.user_id.email}
+</p>
+
+<p>
+${ctx["mail_body"].replace('\n','<br>') | safe}
+</p>
+
+% if ctx["history_mode"] in ('whole'):
+ % for message in object.message_ids:
+ ---- Original Message (${message.date or ''}) ----<br/>
+ ${message.body | safe}
+ % endfor
+% endif
+% if ctx['history_mode'] == 'latest':
+ ---- Original Message (${object.message_ids[0].date or ''}) ----<br/>
+ ${object.message_ids[0].body | safe}
+% endif
+ ]]></field>
+ </record>
+ </data>
+</openerp>
diff --git a/addons/crm_partner_assign/wizard/crm_forward_to_partner.py b/addons/crm_partner_assign/wizard/crm_forward_to_partner.py
index 4ca3ba0a770..81cc09d9717 100644
--- a/addons/crm_partner_assign/wizard/crm_forward_to_partner.py
+++ b/addons/crm_partner_assign/wizard/crm_forward_to_partner.py
@@ -20,30 +20,15 @@
#
##############################################################################
-import re
-import time
-from openerp import tools
-
from openerp.osv import fields, osv
from openerp.tools.translate import _
-class crm_lead_forward_to_partner(osv.osv_memory):
+
+class crm_lead_forward_to_partner(osv.TransientModel):
""" Forward info history to partners. """
_name = 'crm.lead.forward.to.partner'
_inherit = "mail.compose.message"
- def default_get(self, cr, uid, fields, context=None):
- if context is None:
- context = {}
- # set as comment, perform overrided document-like action that calls get_record_data
- old_mode = context.get('default_composition_mode', 'forward')
- context['default_composition_mode'] = 'comment'
- res = super(crm_lead_forward_to_partner, self).default_get(cr, uid, fields, context=context)
- # back to forward mode
- context['default_composition_mode'] = old_mode
- res['composition_mode'] = context['default_composition_mode']
- return res
-
def _get_composition_mode_selection(self, cr, uid, context=None):
composition_mode = super(crm_lead_forward_to_partner, self)._get_composition_mode_selection(cr, uid, context=context)
composition_mode.append(('forward', 'Forward'))
@@ -56,37 +41,54 @@ class crm_lead_forward_to_partner(osv.osv_memory):
'attachment_ids': fields.many2many('ir.attachment',
'lead_forward_to_partner_attachment_rel',
'wizard_id', 'attachment_id', 'Attachments'),
- 'history_mode': fields.selection([('info', 'Case Information'),
+ 'history_mode': fields.selection([('info', 'Internal notes'),
('latest', 'Latest email'), ('whole', 'Whole Story')],
'Send history', required=True),
}
_defaults = {
- 'history_mode': 'latest',
+ 'history_mode': 'info',
}
+ def default_get(self, cr, uid, fields, context=None):
+ if context is None:
+ context = {}
+ # set as comment, perform overrided document-like action that calls get_record_data
+ old_mode = context.get('default_composition_mode', 'forward')
+ context['default_composition_mode'] = 'comment'
+ res = super(crm_lead_forward_to_partner, self).default_get(cr, uid, fields, context=context)
+ # back to forward mode
+ context['default_composition_mode'] = old_mode
+ res['composition_mode'] = context['default_composition_mode']
+ return res
+
def get_record_data(self, cr, uid, model, res_id, context=None):
""" Override of mail.compose.message, to add default values coming
form the related lead.
"""
+ if context is None:
+ context = {}
res = super(crm_lead_forward_to_partner, self).get_record_data(cr, uid, model, res_id, context=context)
if model not in ('crm.lead') or not res_id:
return res
- lead_obj = self.pool.get(model)
- lead = lead_obj.browse(cr, uid, res_id, context=context)
- subject = '%s: %s - %s' % (_('Fwd'), _('Lead forward'), lead.name)
- body = self._get_message_body(cr, uid, lead, 'info', context=context)
- res.update({
- 'subject': subject,
- 'body': body,
- })
+ template_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'crm_partner_assign', 'crm_partner_assign_email_template')[1]
+ context['history_mode'] = context.get('history_mode','whole')
+ mail_body_fields = ['partner_id', 'partner_name', 'title', 'function', 'street', 'street2', 'zip', 'city', 'country_id', 'state_id', 'email_from', 'phone', 'fax', 'mobile', 'description']
+ lead = self.pool.get('crm.lead').browse(cr, uid, res_id, context=context)
+ context['mail_body'] = self.pool.get('crm.lead')._mail_body(cr, uid, lead, mail_body_fields, context=context)
+ template = self.generate_email_for_composer(cr, uid, template_id, res_id, context)
+ res['subject'] = template['subject']
+ res['body'] = template['body']
return res
def on_change_history_mode(self, cr, uid, ids, history_mode, model, res_id, context=None):
""" Update body when changing history_mode """
+ if context is None:
+ context = {}
if model and model == 'crm.lead' and res_id:
lead = self.pool.get(model).browse(cr, uid, res_id, context=context)
- body = self._get_message_body(cr, uid, lead, history_mode, context=context)
+ context['history_mode'] = history_mode
+ body = self.get_record_data(cr, uid, 'crm.lead', res_id, context=context)['body']
return {'value': {'body': body}}
def create(self, cr, uid, values, context=None):
@@ -116,56 +118,7 @@ class crm_lead_forward_to_partner(osv.osv_memory):
value = self.default_get(cr, uid, ['body', 'email_to', 'email_cc', 'subject', 'history_mode'], context=context)
self.write(cr, uid, ids, value, context=context)
- self.send_mail(cr, uid, ids, context=context)
- # for case in lead.browse(cr, uid, lead_ids, context=context):
- # TODO: WHAT TO DO WITH THAT ?
- # if (this.send_to == 'partner' and this.partner_id):
- # lead.assign_partner(cr, uid, [case.id], this.partner_id.id, context=context)
-
- # if this.send_to == 'user':
- # lead.allocate_salesman(cr, uid, [case.id], [this.user_id.id], context=context)
- return res
-
- def _get_info_body(self, cr, uid, lead, context=None):
- field_names = []
- proxy = self.pool.get(lead._name)
- if lead.type == 'opportunity':
- field_names += ['partner_id']
- field_names += [
- 'partner_name' , 'title', 'function', 'street', 'street2',
- 'zip', 'city', 'country_id', 'state_id', 'email_from',
- 'phone', 'fax', 'mobile', 'categ_id', 'description',
- ]
- return proxy._mail_body(cr, uid, lead, field_names, context=context)
-
- def _get_message_body(self, cr, uid, lead, history_mode='whole', context=None):
- """ This function gets whole communication history and returns as top
- posting style
- #1: form a body, based on the lead
- #2: append to the body the communication history, based on the
- history_mode parameter
-
- - info: Forward the case information
- - latest: Send the latest history
- - whole: Send the whole history
-
- :param lead: browse_record on crm.lead
- :param history_mode: 'whole' or 'latest'
- """
- mail_message = self.pool.get('mail.message')
- body = self._get_info_body(cr, uid, lead, context=context)
- if history_mode not in ('whole', 'latest'):
- return body or ''
- for message in lead.message_ids:
- header = '-------- Original Message --------'
- sentdate = 'Date: %s' % (message.date or '')
- desc = '\n%s'%(message.body)
- original = [header, sentdate, desc, '\n']
- original = '\n'.join(original)
- body += original
- if history_mode == 'latest':
- break
- return body or ''
+ return self.send_mail(cr, uid, ids, context=context)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/crm_partner_assign/wizard/crm_forward_to_partner_view.xml b/addons/crm_partner_assign/wizard/crm_forward_to_partner_view.xml
index 0d8f27eec74..0fda8c8beaa 100644
--- a/addons/crm_partner_assign/wizard/crm_forward_to_partner_view.xml
+++ b/addons/crm_partner_assign/wizard/crm_forward_to_partner_view.xml
@@ -15,8 +15,7 @@
<field name="history_mode" colspan="4"
on_change="on_change_history_mode(history_mode, model, res_id)"/>
<field name="subject" colspan="4"/>
- <field name="partner_ids" colspan="4" widget="many2many_tags"
- on_change="onchange_partner_ids(partner_ids)"/>
+ <field name="partner_ids" colspan="4" widget="many2many_tags_email"/>
<notebook colspan="4">
<page string="Body">
<field name="body"/>
diff --git a/addons/document/content_index.py b/addons/document/content_index.py
index 399541e1f59..cde05987f25 100644
--- a/addons/document/content_index.py
+++ b/addons/document/content_index.py
@@ -181,11 +181,11 @@ class contentIndex(object):
res = (mime, fobj.indexContent(content,filename,fname or realfname) )
else:
_logger.debug("Have no object, return (%s, None).", mime)
- res = (mime, None )
+ res = (mime, '')
except Exception:
_logger.exception("Cannot index file %s (%s).",
filename, fname or realfname)
- res = None
+ res = (mime, '')
# If we created a tmp file, unlink it now
if not realfname and fname:
diff --git a/addons/document/std_index.py b/addons/document/std_index.py
index b20874e72f8..dda776b1ec3 100644
--- a/addons/document/std_index.py
+++ b/addons/document/std_index.py
@@ -105,7 +105,7 @@ class DocIndex(indexer):
_logger.warning("Failed attempt to execute antiword (MS Word reader). Antiword is necessary to index the file %s of MIME type %s. Detailed error available at DEBUG level.", fname, self._getMimeTypes()[0])
_logger.debug("Trace of the failed file indexing attempt.", exc_info=True)
- return False
+ return u''
cntIndex.register(DocIndex())
@@ -166,9 +166,14 @@ class PdfIndex(indexer):
return ['.pdf']
def _doIndexFile(self, fname):
- pop = Popen(['pdftotext', '-enc', 'UTF-8', '-nopgbrk', fname, '-'], shell=False, stdout=PIPE)
- (data, _) = pop.communicate()
- return _to_unicode(data)
+ try:
+ pop = Popen(['pdftotext', '-enc', 'UTF-8', '-nopgbrk', fname, '-'], shell=False, stdout=PIPE)
+ (data, _) = pop.communicate()
+ return _to_unicode(data)
+ except OSError:
+ _logger.warning("Failed attempt to execute pdftotext. This program is necessary to index the file %s of MIME type %s. Detailed error available at DEBUG level.", fname, self._getMimeTypes()[0])
+ _logger.debug("Trace of the failed file indexing attempt.", exc_info=True)
+ return u''
cntIndex.register(PdfIndex())
diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py
index 743384059b4..08ef3ec6e98 100644
--- a/addons/email_template/email_template.py
+++ b/addons/email_template/email_template.py
@@ -65,6 +65,7 @@ class email_template(osv.osv):
"Templates for sending email"
_name = "email.template"
_description = 'Email Templates'
+ _order = 'name'
def render_template(self, cr, uid, template, model, res_id, context=None):
"""Render the given template text, replace mako expressions ``${expr}``
@@ -141,7 +142,9 @@ class email_template(osv.osv):
help="If checked, the user's signature will be appended to the text version "
"of the message"),
'subject': fields.char('Subject', translate=True, help="Subject (placeholders may be used here)",),
- 'email_from': fields.char('From', help="Sender address (placeholders may be used here)"),
+ 'email_from': fields.char('From',
+ help="Sender address (placeholders may be used here). If not set, the default "
+ "value will be the author's email alias if configured, or email address."),
'email_to': fields.char('To (Emails)', help="Comma-separated recipient addresses (placeholders may be used here)"),
'email_recipients': fields.char('To (Partners)', help="Comma-separated ids of recipient partners (placeholders may be used here)"),
'email_cc': fields.char('Cc', help="Carbon copy recipients (placeholders may be used here)"),
diff --git a/addons/email_template/email_template_view.xml b/addons/email_template/email_template_view.xml
index 4cddb31c8e1..73e796394a8 100644
--- a/addons/email_template/email_template_view.xml
+++ b/addons/email_template/email_template_view.xml
@@ -28,7 +28,7 @@
<page string="Email Details">
<group>
<group string="Addressing">
- <field name="email_from" required="1"/>
+ <field name="email_from"/>
<field name="email_to"/>
<field name="email_recipients"/>
<field name="email_cc"/>
@@ -43,7 +43,7 @@
<field name="copyvalue"/>
</group>
<group string="Contents" colspan="2">
- <field name="subject" required="1"/>
+ <field name="subject"/>
<field name="body_html" width="250" height="450" nolabel="1" colspan="2" placeholder="Email contents (in raw HTML format)"/>
</group>
</group>
diff --git a/addons/email_template/res_partner.py b/addons/email_template/res_partner.py
index 987fca43219..cb3ef67862f 100644
--- a/addons/email_template/res_partner.py
+++ b/addons/email_template/res_partner.py
@@ -19,7 +19,7 @@
#
##############################################################################
-from openerp.osv import fields,osv
+from openerp.osv import fields, osv
class res_partner(osv.osv):
"""Inherit res.partner to add a generic opt-out field that can be used
@@ -28,8 +28,9 @@ class res_partner(osv.osv):
_inherit = 'res.partner'
_columns = {
- 'opt_out': fields.boolean('Opt-Out', help="If checked, this partner will not receive any automated email " \
- "notifications, such as the availability of invoices."),
+ 'opt_out': fields.boolean('Opt-Out',
+ help="If opt-out is checked, this contact has refused to receive emails for mass mailing and marketing campaign. "
+ "Filter 'Available for Mass Mailing' allows users to filter the partners when performing mass mailing."),
}
_defaults = {
diff --git a/addons/email_template/res_partner_view.xml b/addons/email_template/res_partner_view.xml
index 3d17120d69e..1eab8029cb2 100644
--- a/addons/email_template/res_partner_view.xml
+++ b/addons/email_template/res_partner_view.xml
@@ -11,5 +11,19 @@
</xpath>
</field>
</record>
+
+ <record model="ir.ui.view" id="res_partner_opt_out_search">
+ <field name="name">res.partner.opt_out.search</field>
+ <field name="model">res.partner</field>
+ <field name="inherit_id" ref="base.view_res_partner_filter"/>
+ <field name="arch" type="xml">
+ <filter string="Suppliers" position="after">
+ <separator />
+ <filter string="Available for mass mailing"
+ name='not_opt_out' domain="[('opt_out', '=', False)]"
+ help="Partners that did not ask not to be included in mass mailing campaigns" />
+ </filter>
+ </field>
+ </record>
</data>
</openerp>
diff --git a/addons/email_template/wizard/mail_compose_message.py b/addons/email_template/wizard/mail_compose_message.py
index b39ed4e5639..255f93998a2 100644
--- a/addons/email_template/wizard/mail_compose_message.py
+++ b/addons/email_template/wizard/mail_compose_message.py
@@ -80,7 +80,7 @@ class mail_compose_message(osv.TransientModel):
'datas_fname': attach_fname,
'res_model': model,
'res_id': res_id,
- 'type': 'binary', # override default_type from context, possibly meant for another model!
+ 'type': 'binary', # override default_type from context, possibly meant for another model!
}
values['attachment_ids'].append(ir_attach_obj.create(cr, uid, data_attach, context=context))
else:
@@ -125,19 +125,22 @@ class mail_compose_message(osv.TransientModel):
fields = ['body_html', 'subject', 'email_to', 'email_recipients', 'email_cc', 'attachments']
values = dict((field, template_values[field]) for field in fields if template_values.get(field))
values['body'] = values.pop('body_html', '')
- # transform email_to, email_cc into partner_ids
- values['partner_ids'] = []
+ # transform email_to, email_cc into partner_ids
+ partner_ids = set()
mails = tools.email_split(values.pop('email_to', '') + ' ' + values.pop('email_cc', ''))
for mail in mails:
partner_id = self.pool.get('res.partner').find_or_create(cr, uid, mail, context=context)
- values['partner_ids'].append(partner_id)
+ partner_ids.add(partner_id)
email_recipients = values.pop('email_recipients', '')
if email_recipients:
for partner_id in email_recipients.split(','):
- values['partner_ids'].append(int(partner_id))
-
- values['partner_ids'] = list(set(values['partner_ids']))
+ if partner_id: # placeholders could generate '', 3, 2 due to some empty field values
+ partner_ids.add(int(partner_id))
+ # legacy template behavior: void values do not erase existing values and the
+ # related key is removed from the values dict
+ if partner_ids:
+ values['partner_ids'] = list(partner_ids)
return values
diff --git a/addons/event/event_view.xml b/addons/event/event_view.xml
index 25329e4086a..8efae21f676 100644
--- a/addons/event/event_view.xml
+++ b/addons/event/event_view.xml
@@ -168,7 +168,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
- <field name="message_ids" widget="mail_thread" placeholder="Share a note..."/>
+ <field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
@@ -405,7 +405,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
- <field name="message_ids" widget="mail_thread" placeholder="Share a note..."/>
+ <field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
diff --git a/addons/fetchmail/fetchmail.py b/addons/fetchmail/fetchmail.py
index fdc37e99ee1..4edba8cbe3c 100644
--- a/addons/fetchmail/fetchmail.py
+++ b/addons/fetchmail/fetchmail.py
@@ -78,7 +78,7 @@ class fetchmail_server(osv.osv):
'priority': fields.integer('Server Priority', readonly=True, states={'draft':[('readonly', False)]}, help="Defines the order of processing, "
"lower values mean higher priority"),
'message_ids': fields.one2many('mail.mail', 'fetchmail_server_id', 'Messages', readonly=True),
- 'configuration' : fields.text('Configuration'),
+ 'configuration' : fields.text('Configuration', readonly=True),
'script' : fields.char('Script', readonly=True, size=64),
}
_defaults = {
@@ -113,7 +113,16 @@ class fetchmail_server(osv.osv):
conf['model']=r[0]['model']
values['configuration'] = """Use the below script with the following command line options with your Mail Transport Agent (MTA)
-openerp_mailgate.py -u %(uid)d -p PASSWORD -o %(model)s -d %(dbname)s --host=HOSTNAME --port=PORT
+openerp_mailgate.py --host=HOSTNAME --port=PORT -u %(uid)d -p PASSWORD -d %(dbname)s
+
+Example configuration for the postfix mta running locally:
+
+/etc/postfix/virtual_aliases:
+@youdomain openerp_mailgate@localhost
+
+/etc/aliases:
+openerp_mailgate: "|/path/to/openerp-mailgate.py --host=localhost -u %(uid)d -p PASSWORD -d %(dbname)s"
+
""" % conf
return {'value':values}
diff --git a/addons/hr_evaluation/hr_evaluation_view.xml b/addons/hr_evaluation/hr_evaluation_view.xml
index 52b288fdad5..2462daa6301 100644
--- a/addons/hr_evaluation/hr_evaluation_view.xml
+++ b/addons/hr_evaluation/hr_evaluation_view.xml
@@ -160,7 +160,7 @@
<button name="button_final_validation" string="Validate Appraisal" states="wait" type="object" class="oe_highlight"/>
<button name="button_done" string="Done" states="progress" type="object" class="oe_highlight"/>
<button name="button_draft" string="Reset to Draft" states="cancel" type="object" />
- <button name="button_cancel" string="Cancel" states="draft,wait,progress" type="object"/>
+ <button name="button_cancel" string="Cancel Appraisal" states="draft,wait,progress" type="object"/>
<field name="state" widget="statusbar" statusbar_visible="draft,progress,wait,done" statusbar_colors='{"progress":"blue"}'/>
</header>
<sheet>
@@ -282,7 +282,7 @@
<field name="arch" type="xml">
<form string="Interview Appraisal" version="7.0">
<header>
- <button string="Cancel" name="survey_req_cancel" type="object"
+ <button string="Cancel Survey" name="survey_req_cancel" type="object"
states="draft,waiting_answer" class="oe_left"/>
<button string="Print Survey" name="action_print_survey" type="object"
states="draft" context="{'survey_id': survey_id, 'response_id': [response], 'response_no':0}"
diff --git a/addons/hr_expense/__openerp__.py b/addons/hr_expense/__openerp__.py
index 516af7f9d27..f149bc0c7c6 100644
--- a/addons/hr_expense/__openerp__.py
+++ b/addons/hr_expense/__openerp__.py
@@ -39,14 +39,14 @@ The whole flow is implemented as:
* Draft expense
* Confirmation of the sheet by the employee
* Validation by his manager
-* Validation by the accountant and receipt creation
+* Validation by the accountant and accounting entries creation
This module also uses analytic accounting and is compatible with the invoice on timesheet module so that you are able to automatically re-invoice your customers' expenses if your work by project.
""",
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
'images': ['images/hr_expenses_analysis.jpeg', 'images/hr_expenses.jpeg'],
- 'depends': ['hr', 'account_voucher'],
+ 'depends': ['hr', 'account_accountant'],
'data': [
'security/ir.model.access.csv',
'hr_expense_data.xml',
diff --git a/addons/hr_expense/hr_expense.py b/addons/hr_expense/hr_expense.py
index 0b6ec48974b..15926336697 100644
--- a/addons/hr_expense/hr_expense.py
+++ b/addons/hr_expense/hr_expense.py
@@ -40,7 +40,7 @@ class hr_expense_expense(osv.osv):
if context is None:
context = {}
if not default: default = {}
- default.update({'voucher_id': False, 'date_confirm': False, 'date_valid': False, 'user_valid': False})
+ default.update({'date_confirm': False, 'date_valid': False, 'user_valid': False})
return super(hr_expense_expense, self).copy(cr, uid, id, default, context=context)
def _amount(self, cr, uid, ids, field_name, arg, context=None):
@@ -85,7 +85,6 @@ class hr_expense_expense(osv.osv):
'line_ids': fields.one2many('hr.expense.line', 'expense_id', 'Expense Lines', readonly=True, states={'draft':[('readonly',False)]} ),
'note': fields.text('Note'),
'amount': fields.function(_amount, string='Total Amount', digits_compute=dp.get_precision('Account')),
- 'voucher_id': fields.many2one('account.voucher', "Employee's Receipt"),
'currency_id': fields.many2one('res.currency', 'Currency', required=True, readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
'department_id':fields.many2one('hr.department','Department', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}),
'company_id': fields.many2one('res.company', 'Company', required=True),
@@ -144,92 +143,233 @@ class hr_expense_expense(osv.osv):
def expense_canceled(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'state': 'cancelled'}, context=context)
- def action_receipt_create(self, cr, uid, ids, context=None):
- property_obj = self.pool.get('ir.property')
- sequence_obj = self.pool.get('ir.sequence')
- analytic_journal_obj = self.pool.get('account.analytic.journal')
- account_journal = self.pool.get('account.journal')
- voucher_obj = self.pool.get('account.voucher')
- currency_obj = self.pool.get('res.currency')
+ def account_move_get(self, cr, uid, expense_id, context=None):
+ '''
+ This method prepare the creation of the account move related to the given expense.
+
+ :param expense_id: Id of expense for which we are creating account_move.
+ :return: mapping between fieldname and value of account move to create
+ :rtype: dict
+ '''
+ journal_obj = self.pool.get('account.journal')
+ expense = self.browse(cr, uid, expense_id, context=context)
+ company_id = expense.company_id.id
+ date = expense.date_confirm
+ ref = expense.name
+ journal_id = False
+ if expense.journal_id:
+ journal_id = expense.journal_id.id
+ else:
+ journal_id = journal_obj.search(cr, uid, [('type', '=', 'purchase'), ('company_id', '=', company_id)])
+ if not journal_id:
+ raise osv.except_osv(_('Error!'), _("No expense journal found. Please make sure you have a journal with type 'purchase' configured."))
+ journal_id = journal_id[0]
+ return self.pool.get('account.move').account_move_prepare(cr, uid, journal_id, date=date, ref=ref, company_id=company_id, context=context)
+
+ def line_get_convert(self, cr, uid, x, part, date, context=None):
+ partner_id = self.pool.get('res.partner')._find_accounting_partner(part).id
+ return {
+ 'date_maturity': x.get('date_maturity', False),
+ 'partner_id': partner_id,
+ 'name': x['name'][:64],
+ 'date': date,
+ 'debit': x['price']>0 and x['price'],
+ 'credit': x['price']<0 and -x['price'],
+ 'account_id': x['account_id'],
+ 'analytic_lines': x.get('analytic_lines', False),
+ 'amount_currency': x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
+ 'currency_id': x.get('currency_id', False),
+ 'tax_code_id': x.get('tax_code_id', False),
+ 'tax_amount': x.get('tax_amount', False),
+ 'ref': x.get('ref', False),
+ 'quantity': x.get('quantity',1.00),
+ 'product_id': x.get('product_id', False),
+ 'product_uom_id': x.get('uos_id', False),
+ 'analytic_account_id': x.get('account_analytic_id', False),
+ }
+
+ def compute_expense_totals(self, cr, uid, exp, company_currency, ref, account_move_lines, context=None):
+ '''
+ internal method used for computation of total amount of an expense in the company currency and
+ in the expense currency, given the account_move_lines that will be created. It also do some small
+ transformations at these account_move_lines (for multi-currency purposes)
+
+ :param account_move_lines: list of dict
+ :rtype: tuple of 3 elements (a, b ,c)
+ a: total in company currency
+ b: total in hr.expense currency
+ c: account_move_lines potentially modified
+ '''
+ cur_obj = self.pool.get('res.currency')
if context is None:
- context = {}
- for exp in self.browse(cr, uid, ids, context=context):
- company_id = exp.company_id.id
- lines = []
- total = 0.0
- ctx = context.copy()
- ctx.update({'date': exp.date})
- journal = False
- if exp.journal_id:
- journal = exp.journal_id
+ context={}
+ context.update({'date': exp.date_confirm or time.strftime('%Y-%m-%d')})
+ total = 0.0
+ total_currency = 0.0
+ for i in account_move_lines:
+ if exp.currency_id.id != company_currency:
+ i['currency_id'] = exp.currency_id.id
+ i['amount_currency'] = i['price']
+ i['price'] = cur_obj.compute(cr, uid, exp.currency_id.id,
+ company_currency, i['price'],
+ context=context)
else:
- journal_id = voucher_obj._get_journal(cr, uid, context={'type': 'purchase', 'company_id': company_id})
- if journal_id:
- journal = account_journal.browse(cr, uid, journal_id, context=context)
- if not journal:
- raise osv.except_osv(_('Error!'), _("No expense journal found. Please make sure you have a journal with type 'purchase' configured."))
- for line in exp.line_ids:
- if line.product_id:
- acc = line.product_id.property_account_expense
- if not acc:
- acc = line.product_id.categ_id.property_account_expense_categ
- else:
- acc = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category', context={'force_company': company_id})
- if not acc:
- raise osv.except_osv(_('Error!'), _('Please configure Default Expense account for Product purchase: `property_account_expense_categ`.'))
- total_amount = line.total_amount
- if journal.currency:
- if exp.currency_id != journal.currency:
- total_amount = currency_obj.compute(cr, uid, exp.currency_id.id, journal.currency.id, total_amount, context=ctx)
- elif exp.currency_id != exp.company_id.currency_id:
- total_amount = currency_obj.compute(cr, uid, exp.currency_id.id, exp.company_id.currency_id.id, total_amount, context=ctx)
- lines.append((0, False, {
- 'name': line.name,
- 'account_id': acc.id,
- 'account_analytic_id': line.analytic_account.id,
- 'amount': total_amount,
- 'type': 'dr'
- }))
- total += total_amount
+ i['amount_currency'] = False
+ i['currency_id'] = False
+ total -= i['price']
+ total_currency -= i['amount_currency'] or i['price']
+ return total, total_currency, account_move_lines
+
+ def action_move_create(self, cr, uid, ids, context=None):
+ '''
+ main function that is called when trying to create the accounting entries related to an expense
+ '''
+ move_obj = self.pool.get('account.move')
+ for exp in self.browse(cr, uid, ids, context=context):
if not exp.employee_id.address_home_id:
raise osv.except_osv(_('Error!'), _('The employee must have a home address.'))
+ company_currency = exp.company_id.currency_id.id
+ diff_currency_p = exp.currency_id.id <> company_currency
+
+ #create the move that will contain the accounting entries
+ move_id = move_obj.create(cr, uid, self.account_move_get(cr, uid, exp.id, context=context), context=context)
+
+ #one account.move.line per expense line (+taxes..)
+ eml = self.move_line_get(cr, uid, exp.id, context=context)
+
+ #create one more move line, a counterline for the total on payable account
+ total, total_currency, eml = self.compute_expense_totals(cr, uid, exp, company_currency, exp.name, eml, context=context)
acc = exp.employee_id.address_home_id.property_account_payable.id
- voucher = {
- 'name': exp.name or '/',
- 'reference': sequence_obj.get(cr, uid, 'hr.expense.invoice'),
- 'account_id': acc,
- 'type': 'purchase',
- 'partner_id': exp.employee_id.address_home_id.id,
- 'company_id': company_id,
- 'line_ids': lines,
- 'amount': total,
- 'journal_id': journal.id,
- }
- if journal and not journal.analytic_journal_id:
- analytic_journal_ids = analytic_journal_obj.search(cr, uid, [('type','=','purchase')], context=context)
- if analytic_journal_ids:
- account_journal.write(cr, uid, [journal.id], {'analytic_journal_id': analytic_journal_ids[0]}, context=context)
- voucher_id = voucher_obj.create(cr, uid, voucher, context=context)
- self.write(cr, uid, [exp.id], {'voucher_id': voucher_id, 'state': 'done'}, context=context)
+ eml.append({
+ 'type': 'dest',
+ 'name': '/',
+ 'price': total,
+ 'account_id': acc,
+ 'date_maturity': exp.date_confirm,
+ 'amount_currency': diff_currency_p and total_currency or False,
+ 'currency_id': diff_currency_p and exp.currency_id.id or False,
+ 'ref': exp.name
+ })
+
+ #convert eml into an osv-valid format
+ lines = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, exp.employee_id.address_home_id, exp.date_confirm, context=context)), eml)
+ move_obj.write(cr, uid, [move_id], {'line_id': lines}, context=context)
+ self.write(cr, uid, ids, {'account_move_id': move_id, 'state': 'done'}, context=context)
return True
-
- def action_view_receipt(self, cr, uid, ids, context=None):
+
+ def move_line_get(self, cr, uid, expense_id, context=None):
+ res = []
+ tax_obj = self.pool.get('account.tax')
+ cur_obj = self.pool.get('res.currency')
+ if context is None:
+ context = {}
+ exp = self.browse(cr, uid, expense_id, context=context)
+ company_currency = exp.company_id.currency_id.id
+
+ for line in exp.line_ids:
+ mres = self.move_line_get_item(cr, uid, line, context)
+ if not mres:
+ continue
+ res.append(mres)
+ tax_code_found= False
+
+ #Calculate tax according to default tax on product
+
+ taxes = []
+ #Taken from product_id_onchange in account.invoice
+ if line.product_id:
+ fposition_id = False
+ fpos_obj = self.pool.get('account.fiscal.position')
+ fpos = fposition_id and fpos_obj.browse(cr, uid, fposition_id, context=context) or False
+ product = line.product_id
+ taxes = product.supplier_taxes_id
+ #If taxes are not related to the product, maybe they are in the account
+ if not taxes:
+ a = product.property_account_expense.id #Why is not there a check here?
+ if not a:
+ a = product.categ_id.property_account_expense_categ.id
+ a = fpos_obj.map_account(cr, uid, fpos, a)
+ taxes = a and self.pool.get('account.account').browse(cr, uid, a, context=context).tax_ids or False
+ tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes)
+ if not taxes:
+ continue
+ #Calculating tax on the line and creating move?
+ for tax in tax_obj.compute_all(cr, uid, taxes,
+ line.unit_amount ,
+ line.unit_quantity, line.product_id,
+ exp.user_id.partner_id)['taxes']:
+ tax_code_id = tax['base_code_id']
+ tax_amount = line.total_amount * tax['base_sign']
+ if tax_code_found:
+ if not tax_code_id:
+ continue
+ res.append(self.move_line_get_item(cr, uid, line, context))
+ res[-1]['price'] = 0.0
+ res[-1]['account_analytic_id'] = False
+ elif not tax_code_id:
+ continue
+ tax_code_found = True
+ res[-1]['tax_code_id'] = tax_code_id
+ res[-1]['tax_amount'] = cur_obj.compute(cr, uid, exp.currency_id.id, company_currency, tax_amount, context={'date': exp.date_confirm})
+
+ #Will create the tax here as we don't have the access
+ assoc_tax = {
+ 'type':'tax',
+ 'name':tax['name'],
+ 'price_unit': tax['price_unit'],
+ 'quantity': 1,
+ 'price': tax['amount'] * tax['base_sign'] or 0.0,
+ 'account_id': tax['account_collected_id'],
+ 'tax_code_id': tax['tax_code_id'],
+ 'tax_amount': tax['amount'] * tax['base_sign'],
+ }
+ res.append(assoc_tax)
+ return res
+
+ def move_line_get_item(self, cr, uid, line, context=None):
+ company = line.expense_id.company_id
+ property_obj = self.pool.get('ir.property')
+ if line.product_id:
+ acc = line.product_id.property_account_expense
+ if not acc:
+ acc = line.product_id.categ_id.property_account_expense_categ
+ else:
+ acc = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category', context={'force_company': company.id})
+ if not acc:
+ raise osv.except_osv(_('Error!'), _('Please configure Default Expense account for Product purchase: `property_account_expense_categ`.'))
+ return {
+ 'type':'src',
+ 'name': line.name.split('\n')[0][:64],
+ 'price_unit':line.unit_amount,
+ 'quantity':line.unit_quantity,
+ 'price':line.total_amount,
+ 'account_id':acc.id,
+ 'product_id':line.product_id.id,
+ 'uos_id':line.uom_id.id,
+ 'account_analytic_id':line.analytic_account.id,
+ }
+
+ def action_view_move(self, cr, uid, ids, context=None):
'''
- This function returns an action that display existing receipt of given expense ids.
+ This function returns an action that display existing account.move of given expense ids.
'''
assert len(ids) == 1, 'This option should only be used for a single id at a time'
- voucher_id = self.browse(cr, uid, ids[0], context=context).voucher_id.id
- res = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account_voucher', 'view_purchase_receipt_form')
+ expense = self.browse(cr, uid, ids[0], context=context)
+ assert expense.account_move_id
+ try:
+ dummy, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'view_move_form')
+ except ValueError, e:
+ view_id = False
result = {
- 'name': _('Expense Receipt'),
+ 'name': _('Expense Account Move'),
'view_type': 'form',
'view_mode': 'form',
- 'view_id': res and res[1] or False,
- 'res_model': 'account.voucher',
+ 'view_id': view_id,
+ 'res_model': 'account.move',
'type': 'ir.actions.act_window',
'nodestroy': True,
'target': 'current',
- 'res_id': voucher_id,
+ 'res_id': expense.account_move_id.id,
}
return result
diff --git a/addons/hr_expense/hr_expense_view.xml b/addons/hr_expense/hr_expense_view.xml
index 0324388786f..49ea2f0ab9e 100644
--- a/addons/hr_expense/hr_expense_view.xml
+++ b/addons/hr_expense/hr_expense_view.xml
@@ -67,7 +67,7 @@
<button name="refuse" states="confirm,accepted" string="Refuse" type="workflow" groups="base.group_hr_user" />
<button name="draft" states="confirm,cancelled" string="Set to Draft" type="workflow" groups="base.group_hr_user" />
<button name="done" states="accepted" string="Generate Accounting Entries" type="workflow" groups="account.group_account_invoice" class="oe_highlight"/>
- <button name="action_view_receipt" states="done" string="Open Receipt" type="object"/>
+ <button name="action_view_move" states="done" string="Open Accounting Entries" type="object" groups="account.group_account_invoice"/>
<field name="state" widget="statusbar" statusbar_visible="draft,confirm,accepted,done" statusbar_colors='{"confirm":"blue","cancelled":"red"}'/>
</header>
<sheet>
@@ -133,7 +133,7 @@
<group>
<group string="Accounting Data">
<field name="journal_id" widget="selection" domain="[('type', '=', 'purchase')]"/>
- <field name="voucher_id" context="{'form_view_ref': 'account_voucher.view_purchase_receipt_form'}"/>
+ <field name="account_move_id"/>
</group>
</group>
</page>
diff --git a/addons/hr_expense/hr_expense_workflow.xml b/addons/hr_expense/hr_expense_workflow.xml
index 2e085071207..c01bb4cd14b 100644
--- a/addons/hr_expense/hr_expense_workflow.xml
+++ b/addons/hr_expense/hr_expense_workflow.xml
@@ -43,7 +43,7 @@
<field name="wkf_id" ref="wkf_expenses"/>
<field name="name">done</field>
<field name="kind">function</field>
- <field name="action">action_receipt_create()</field>
+ <field name="action">action_move_create()</field>
</record>
<record id="t1" model="workflow.transition">
diff --git a/addons/hr_expense/report/hr_expense_report.py b/addons/hr_expense/report/hr_expense_report.py
index 6f18c668413..2adc11535ff 100644
--- a/addons/hr_expense/report/hr_expense_report.py
+++ b/addons/hr_expense/report/hr_expense_report.py
@@ -43,7 +43,6 @@ class hr_expense_report(osv.osv):
'employee_id': fields.many2one('hr.employee', "Employee's Name", readonly=True),
'date_confirm': fields.date('Confirmation Date', readonly=True),
'date_valid': fields.date('Validation Date', readonly=True),
- 'voucher_id': fields.many2one('account.voucher', 'Receipt', readonly=True),
'department_id':fields.many2one('hr.department','Department', readonly=True),
'company_id':fields.many2one('res.company', 'Company', readonly=True),
'user_id':fields.many2one('res.users', 'Validation User', readonly=True),
@@ -77,7 +76,6 @@ class hr_expense_report(osv.osv):
s.currency_id,
to_date(to_char(s.date_confirm, 'dd-MM-YYYY'),'dd-MM-YYYY') as date_confirm,
to_date(to_char(s.date_valid, 'dd-MM-YYYY'),'dd-MM-YYYY') as date_valid,
- s.voucher_id,
s.user_valid as user_id,
s.department_id,
to_char(date_trunc('day',s.create_date), 'YYYY') as year,
@@ -107,7 +105,6 @@ class hr_expense_report(osv.osv):
to_date(to_char(s.date_valid, 'dd-MM-YYYY'),'dd-MM-YYYY'),
l.product_id,
l.analytic_account,
- s.voucher_id,
s.currency_id,
s.user_valid,
s.department_id,
diff --git a/addons/hr_expense/report/hr_expense_report_view.xml b/addons/hr_expense/report/hr_expense_report_view.xml
index 9f94c264c5e..be1588f3c3b 100644
--- a/addons/hr_expense/report/hr_expense_report_view.xml
+++ b/addons/hr_expense/report/hr_expense_report_view.xml
@@ -12,7 +12,6 @@
<field name="year" invisible="1"/>
<field name="month" invisible="1"/>
<field name="day" invisible="1"/>
- <field name="voucher_id" invisible="1"/>
<field name="analytic_account" invisible="1" groups="analytic.group_analytic_accounting"/>
<field name="department_id" invisible="1"/>
<field name="company_id" invisible="1"/>
diff --git a/addons/hr_expense/test/expense_process.yml b/addons/hr_expense/test/expense_process.yml
index b563f30afa9..8c4e09f47f0 100644
--- a/addons/hr_expense/test/expense_process.yml
+++ b/addons/hr_expense/test/expense_process.yml
@@ -26,11 +26,6 @@
!python {model: hr.expense.expense}: |
sep_expenses = self.browse(cr, uid, ref("sep_expenses"), context=context)
assert sep_expenses.state == 'done', "Expense should be in 'Done' state."
- assert sep_expenses.voucher_id, "Expense should have link of Purchase Receipt."
- assert sep_expenses.voucher_id.type == 'purchase', "Receipt type is not purchase receipt."
- assert sep_expenses.voucher_id.amount == sep_expenses.amount,"Receipt total amount is not correspond with expense total."
- assert len(sep_expenses.voucher_id.line_dr_ids) == len(sep_expenses.line_ids),"Lines of Receipt and expense line are not correspond."
-
-
I duplicate the expenses and cancel duplicated.
-
diff --git a/addons/hr_payroll/hr_payroll_view.xml b/addons/hr_payroll/hr_payroll_view.xml
index c87d36de591..ccd87ba2616 100644
--- a/addons/hr_payroll/hr_payroll_view.xml
+++ b/addons/hr_payroll/hr_payroll_view.xml
@@ -217,7 +217,7 @@
<button string="Refund" name="refund_sheet" states="confirm,done" type='object' />
<button string="Set to Draft" name="draft" states="cancel"/>
<button string="Compute Sheet" name="compute_sheet" type="object" states="draft" class="oe_highlight"/>
- <button string="Cancel" name="cancel_sheet" states="draft,hr_check,confirm,verify"/>
+ <button string="Cancel Payslip" name="cancel_sheet" states="draft,hr_check,confirm,verify"/>
<field name="state" widget="statusbar" statusbar_visible="draft,confirm"/>
</header>
<sheet>
diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py
index ff1010a45d5..deccf10619f 100644
--- a/addons/hr_recruitment/hr_recruitment.py
+++ b/addons/hr_recruitment/hr_recruitment.py
@@ -340,6 +340,15 @@ class hr_applicant(base_stage, osv.Model):
value = self.pool.get("survey").action_print_survey(cr, uid, ids, context=context)
return value
+ def message_get_suggested_recipients(self, cr, uid, ids, context=None):
+ recipients = super(hr_applicant, self).message_get_suggested_recipients(cr, uid, ids, context=context)
+ for applicant in self.browse(cr, uid, ids, context=context):
+ if applicant.partner_id:
+ self._message_add_suggested_recipient(cr, uid, recipients, applicant, partner=applicant.partner_id, reason=_('Contact'))
+ elif applicant.email_from:
+ self._message_add_suggested_recipient(cr, uid, recipients, applicant, email=applicant.email_from, reason=_('Contact Email'))
+ return recipients
+
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.
diff --git a/addons/hr_recruitment/hr_recruitment_view.xml b/addons/hr_recruitment/hr_recruitment_view.xml
index d97d8985752..ccf5c8f7934 100644
--- a/addons/hr_recruitment/hr_recruitment_view.xml
+++ b/addons/hr_recruitment/hr_recruitment_view.xml
@@ -152,7 +152,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
- <field name="message_ids" widget="mail_thread" placeholder="Share a message..."/>
+ <field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
@@ -238,6 +238,7 @@
<field name="title_action"/>
<field name="department_id"/>
<field name="categ_ids"/>
+ <field name="message_summary"/>
<templates>
<t t-name="kanban-tooltip">
<ul class="oe_kanban_tooltip">
@@ -283,6 +284,7 @@
</div>
<div class="oe_kanban_footer_left" style="margin-top:5px;">
+ <t t-raw="record.message_summary.raw_value"/>
<field name="categ_ids"/>
</div>
</div>
diff --git a/addons/l10n_fr_hr_payroll/report/fiche_paye.rml b/addons/l10n_fr_hr_payroll/report/fiche_paye.rml
index 76c6eb9dedf..2d1a6bff8b2 100644
--- a/addons/l10n_fr_hr_payroll/report/fiche_paye.rml
+++ b/addons/l10n_fr_hr_payroll/report/fiche_paye.rml
@@ -348,10 +348,10 @@
<para style="terp_default_7">[[ p.total or '' ]]</para>
</td>
<td>
- <para style="terp_default_7">[[ get_employer_line(o, p).rate or '']] </para>
+ <para style="terp_default_7">[[ get_employer_line(o, p) and get_employer_line(o, p).rate or '']] </para>
</td>
<td>
- <para style="terp_default_7">[[ get_employer_line(o,p).total or '' ]]</para>
+ <para style="terp_default_7">[[ get_employer_line(o,p) and get_employer_line(o,p).total or '' ]]</para>
</td>
</tr>
</blockTable>
diff --git a/addons/l10n_in_hr_payroll/l10n_in_hr_payroll_view.xml b/addons/l10n_in_hr_payroll/l10n_in_hr_payroll_view.xml
index 37d2f865770..51c63c730ff 100644
--- a/addons/l10n_in_hr_payroll/l10n_in_hr_payroll_view.xml
+++ b/addons/l10n_in_hr_payroll/l10n_in_hr_payroll_view.xml
@@ -80,7 +80,7 @@
<header>
<button name="confirm_sheet" string="Confirm Sheet" states="draft" type="object" class="oe_highlight"/>
<button name="compute_advice" string="Compute Advice" states="draft" type="object" class="oe_highlight"/>
- <button name="cancel_sheet" string="Cancel" states="draft" type="object"/>
+ <button name="cancel_sheet" string="Cancel Advice" states="draft" type="object"/>
<button name="set_to_draft" string="Set to Draft" states="cancel,confirm" type="object"/>
<div class="oe_right">
<field name="state" widget="statusbar" nolabel="1" statusbar_visible="draft"/>
diff --git a/addons/lunch/lunch_demo.xml b/addons/lunch/lunch_demo.xml
index 7488208fd42..7e5e2f3ac8f 100644
--- a/addons/lunch/lunch_demo.xml
+++ b/addons/lunch/lunch_demo.xml
@@ -2,9 +2,6 @@
<openerp>
<data noupdate="1">
- <record id="base.user_root" model="res.users">
- <field name="groups_id" eval="[(4,ref('lunch.group_lunch_manager'))]"/>
- </record>
<record id="base.user_demo" model="res.users">
<field name="groups_id" eval="[(4,ref('lunch.group_lunch_user'))]"/>
</record>
diff --git a/addons/lunch/lunch_view.xml b/addons/lunch/lunch_view.xml
index 4a70fa896e4..2f3af2f461d 100644
--- a/addons/lunch/lunch_view.xml
+++ b/addons/lunch/lunch_view.xml
@@ -283,7 +283,7 @@
<field name="name">Order lines Tree</field>
<field name="model">lunch.order.line</field>
<field name="arch" type="xml">
- <tree string="Order lines Tree">
+ <tree string="Order lines Tree" create="false" edit="false">
<field name='date'/>
<field name='user_id'/>
<field name='supplier' invisible='1'/>
diff --git a/addons/lunch/security/lunch_security.xml b/addons/lunch/security/lunch_security.xml
index 654f844c876..e01fab71ea2 100644
--- a/addons/lunch/security/lunch_security.xml
+++ b/addons/lunch/security/lunch_security.xml
@@ -14,6 +14,7 @@
<field name="name">Manager</field>
<field name="implied_ids" eval="[(4, ref('group_lunch_user'))]"/>
<field name="category_id" ref="module_lunch_category"/>
+ <field name="users" eval="[(4, ref('base.user_root'))]"/>
</record>
</data>
</openerp>
diff --git a/addons/mail/mail_followers.py b/addons/mail/mail_followers.py
index db48db2ec61..dea0afafd8e 100644
--- a/addons/mail/mail_followers.py
+++ b/addons/mail/mail_followers.py
@@ -77,20 +77,25 @@ class mail_notification(osv.Model):
if not cr.fetchone():
cr.execute('CREATE INDEX mail_notification_partner_id_read_starred_message_id ON mail_notification (partner_id, read, starred, message_id)')
- def get_partners_to_notify(self, cr, uid, message, context=None):
+ def get_partners_to_notify(self, cr, uid, message, partners_to_notify=None, context=None):
""" Return the list of partners to notify, based on their preferences.
:param browse_record message: mail.message to notify
+ :param list partners_to_notify: optional list of partner ids restricting
+ the notifications to process
"""
notify_pids = []
for notification in message.notification_ids:
if notification.read:
continue
partner = notification.partner_id
+ # If partners_to_notify specified: restrict to them
+ if partners_to_notify and partner.id not in partners_to_notify:
+ continue
# Do not send to partners without email address defined
if not partner.email:
continue
- # Partner does not want to receive any emails
+ # Partner does not want to receive any emails or is opt-out
if partner.notification_email_send == 'none':
continue
# Partner wants to receive only emails and comments
@@ -139,16 +144,35 @@ class mail_notification(osv.Model):
return footer
- def _notify(self, cr, uid, msg_id, context=None):
- """ Send by email the notification depending on the user preferences """
+ def _notify(self, cr, uid, msg_id, partners_to_notify=None, context=None):
+ """ Send by email the notification depending on the user preferences
+
+ :param list partners_to_notify: optional list of partner ids restricting
+ the notifications to process
+ """
if context is None:
context = {}
+ mail_message_obj = self.pool.get('mail.message')
+
+ # optional list of partners to notify: subscribe them if not already done or update the notification
+ if partners_to_notify:
+ notifications_to_update = []
+ notified_partners = []
+ notif_ids = self.search(cr, SUPERUSER_ID, [('message_id', '=', msg_id), ('partner_id', 'in', partners_to_notify)], context=context)
+ for notification in self.browse(cr, SUPERUSER_ID, notif_ids, context=context):
+ notified_partners.append(notification.partner_id.id)
+ notifications_to_update.append(notification.id)
+ partners_to_notify = filter(lambda item: item not in notified_partners, partners_to_notify)
+ if notifications_to_update:
+ self.write(cr, SUPERUSER_ID, notifications_to_update, {'read': False}, context=context)
+ mail_message_obj.write(cr, uid, msg_id, {'notified_partner_ids': [(4, id) for id in partners_to_notify]}, context=context)
+
# mail_notify_noemail (do not send email) or no partner_ids: do not send, return
if context.get('mail_notify_noemail'):
return True
# browse as SUPERUSER_ID because of access to res_partner not necessarily allowed
msg = self.pool.get('mail.message').browse(cr, SUPERUSER_ID, msg_id, context=context)
- notify_partner_ids = self.get_partners_to_notify(cr, uid, msg, context=context)
+ notify_partner_ids = self.get_partners_to_notify(cr, uid, msg, partners_to_notify=partners_to_notify, context=context)
if not notify_partner_ids:
return True
@@ -172,17 +196,13 @@ class mail_notification(osv.Model):
mail_values = {
'mail_message_id': msg.id,
- 'email_to': [],
'auto_delete': True,
'body_html': body_html,
'email_from': email_from,
- 'state': 'outgoing',
}
- mail_values['email_to'] = ', '.join(mail_values['email_to'])
mail_mail = self.pool.get('mail.mail')
email_notif_id = mail_mail.create(cr, uid, mail_values, context=context)
try:
return mail_mail.send(cr, uid, [email_notif_id], recipient_ids=notify_partner_ids, context=context)
except Exception:
return False
-
diff --git a/addons/mail/mail_group_view.xml b/addons/mail/mail_group_view.xml
index fba192b18bc..77c0b8f6c86 100644
--- a/addons/mail/mail_group_view.xml
+++ b/addons/mail/mail_group_view.xml
@@ -106,7 +106,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
- <field name="message_ids" widget="mail_thread" options='{"thread_level": 1}' placeholder="Send a message to the group..."/>
+ <field name="message_ids" widget="mail_thread" options='{"thread_level": 1}' placeholder="Send a message to the group"/>
</div>
</form>
</field>
diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py
index 231c603e151..a1ee03f4ef4 100644
--- a/addons/mail/mail_mail.py
+++ b/addons/mail/mail_mail.py
@@ -201,20 +201,31 @@ class mail_mail(osv.Model):
return body
def send_get_mail_reply_to(self, cr, uid, mail, partner=None, context=None):
- """ Return a specific ir_email body. The main purpose of this method
- is to be inherited by Portal, to add a link for signing in, in
- each notification email a partner receives.
+ """ Return a specific ir_email reply_to.
:param browse_record mail: mail.mail browse_record
:param browse_record partner: specific recipient partner
"""
if mail.reply_to:
return mail.reply_to
- if not mail.model or not mail.res_id:
- return False
- if not hasattr(self.pool.get(mail.model), 'message_get_reply_to'):
- return False
- return self.pool.get(mail.model).message_get_reply_to(cr, uid, [mail.res_id], context=context)[0]
+ email_reply_to = False
+
+ # if model and res_id: try to use ``message_get_reply_to`` that returns the document alias
+ if mail.model and mail.res_id and hasattr(self.pool.get(mail.model), 'message_get_reply_to'):
+ email_reply_to = self.pool.get(mail.model).message_get_reply_to(cr, uid, [mail.res_id], context=context)[0]
+ # no alias reply_to -> reply_to will be the email_from, only the email part
+ if not email_reply_to and mail.email_from:
+ emails = tools.email_split(mail.email_from)
+ if emails:
+ email_reply_to = emails[0]
+
+ # format 'Document name <email_address>'
+ if email_reply_to and mail.model and mail.res_id:
+ document_name = self.pool.get(mail.model).name_get(cr, SUPERUSER_ID, [mail.res_id], context=context)[0]
+ if document_name:
+ email_reply_to = _('Followers of %s <%s>') % (document_name[1], email_reply_to)
+
+ return email_reply_to
def send_get_email_dict(self, cr, uid, mail, partner=None, context=None):
""" Return a dictionary for specific email values, depending on a
diff --git a/addons/mail/mail_mail_view.xml b/addons/mail/mail_mail_view.xml
index 72d41e33a84..fb2b66adb36 100644
--- a/addons/mail/mail_mail_view.xml
+++ b/addons/mail/mail_mail_view.xml
@@ -75,7 +75,7 @@
<field name="type" invisible="1"/>
<button name="send" string="Send Now" type="object" icon="gtk-media-play" states='outgoing'/>
<button name="mark_outgoing" string="Retry" type="object" icon="gtk-redo" states='exception,cancel'/>
- <button name="cancel" string="Cancel" type="object" icon="terp-gtk-stop" states='outgoing'/>
+ <button name="cancel" string="Cancel Email" type="object" icon="terp-gtk-stop" states='outgoing'/>
</tree>
</field>
</record>
@@ -102,11 +102,10 @@
<field name="res_id"/>
</group>
<group expand="0" string="Group By...">
- <filter string="Status" icon="terp-stock_effects-object-colorize" domain="[]" context="{'group_by':'state'}"/>
- <filter string="Partner" icon="terp-partner" domain="[]" context="{'group_by':'partner_id'}"/>
- <filter string="User" name="User" icon="terp-personal" context="{'group_by':'user_id'}"/>
- <filter string="Thread" icon="terp-mail-" domain="[]" context="{'group_by':'message_id'}"/>
- <filter string="Month" help="Creation Month" icon="terp-go-month" domain="[]" context="{'group_by':'date'}"/>
+ <filter string="Status" name="status" domain="[]" context="{'group_by':'state'}"/>
+ <filter string="Author" name="author" context="{'group_by':'author_id'}"/>
+ <filter string="Thread" name="thread" domain="[]" context="{'group_by':'message_id'}"/>
+ <filter string="Month" name="month" help="Creation Month" domain="[]" context="{'group_by':'date'}"/>
</group>
</search>
</field>
diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py
index 77573f0d2f8..2f56e620543 100644
--- a/addons/mail/mail_message.py
+++ b/addons/mail/mail_message.py
@@ -301,8 +301,8 @@ class mail_message(osv.Model):
for key, message in message_tree.iteritems():
if message.author_id:
partner_ids |= set([message.author_id.id])
- if message.partner_ids:
- partner_ids |= set([partner.id for partner in message.partner_ids])
+ if message.notified_partner_ids:
+ partner_ids |= set([partner.id for partner in message.notified_partner_ids])
if message.attachment_ids:
attachment_ids |= set([attachment.id for attachment in message.attachment_ids])
# Read partners as SUPERUSER -> display the names like classic m2o even if no access
@@ -322,7 +322,7 @@ class mail_message(osv.Model):
else:
author = (0, message.email_from)
partner_ids = []
- for partner in message.partner_ids:
+ for partner in message.notified_partner_ids:
if partner.id in partner_tree:
partner_ids.append(partner_tree[partner.id])
attachment_ids = []
@@ -861,7 +861,7 @@ class mail_message(osv.Model):
# message has no subtype_id: pure log message -> no partners, no one notified
if not message.subtype_id:
return True
-
+
# all followers of the mail.message document have to be added as partners and notified
if message.model and message.res_id:
fol_obj = self.pool.get("mail.followers")
@@ -884,8 +884,7 @@ class mail_message(osv.Model):
# notify
if partners_to_notify:
- self.write(cr, SUPERUSER_ID, [newid], {'notified_partner_ids': [(4, p.id) for p in partners_to_notify]}, context=context)
- notification_obj._notify(cr, uid, newid, context=context)
+ notification_obj._notify(cr, uid, newid, partners_to_notify=[p.id for p in partners_to_notify], context=context)
message.refresh()
# An error appear when a user receive a notification without notifying
diff --git a/addons/mail/mail_message_view.xml b/addons/mail/mail_message_view.xml
index 7d2e9483773..8f2fb3f1020 100644
--- a/addons/mail/mail_message_view.xml
+++ b/addons/mail/mail_message_view.xml
@@ -59,12 +59,12 @@
<field name="type"/>
<field name="author_id"/>
<field name="partner_ids"/>
- <filter string="Read"
- name="message_read" help="Show messages to read"
- domain="[('to_read', '=', False)]"/>
- <filter string="Unread"
+ <filter string="To Read"
name="message_unread" help="Show messages to read"
domain="[('to_read', '=', True)]"/>
+ <filter string="Read"
+ name="message_read" help="Show already read messages"
+ domain="[('to_read', '=', False)]"/>
<separator/>
<filter string="Comments"
name="comments" help="Comments"
diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py
index 64b1410508d..1d1131faccd 100644
--- a/addons/mail/mail_thread.py
+++ b/addons/mail/mail_thread.py
@@ -93,7 +93,7 @@ class mail_thread(osv.AbstractModel):
""" Computes:
- message_unread: has uid unread message for the document
- message_summary: html snippet summarizing the Chatter for kanban views """
- res = dict((id, dict(message_unread=False, message_summary='')) for id in ids)
+ res = dict((id, dict(message_unread=False, message_unread_count=0, message_summary=' ')) for id in ids)
user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
# search for unread messages, directly in SQL to improve performances
@@ -102,14 +102,14 @@ class mail_thread(osv.AbstractModel):
ON (n.message_id = m.id AND n.partner_id = %s AND (n.read = False or n.read IS NULL))
WHERE m.model = %s AND m.res_id in %s""",
(user_pid, self._name, tuple(ids),))
- msg_ids = [result[0] for result in cr.fetchall()]
- for msg_id in msg_ids:
- res[msg_id]['message_unread'] = True
-
- for thread in self.browse(cr, uid, ids, context=context):
- cls = res[thread.id]['message_unread'] and ' class="oe_kanban_mail_new"' or ''
- res[thread.id]['message_summary'] = "<span%s><span class='oe_e'>9</span> %d</span> <span><span class='oe_e'>+</span> %d</span>" % (cls, len(thread.message_ids), len(thread.message_follower_ids))
+ for result in cr.fetchall():
+ res[result[0]]['message_unread'] = True
+ res[result[0]]['message_unread_count'] += 1
+ for id in ids:
+ if res[id]['message_unread_count']:
+ title = res[id]['message_unread_count'] > 1 and _("You have %d unread messages") % res[id]['message_unread_count'] or _("You have one unread message")
+ res[id]['message_summary'] = "<span class='oe_kanban_mail_new' title='%s'><span class='oe_e'>9</span> %d %s</span>" % (title, res[id].pop('message_unread_count'), _("New"))
return res
def _get_subscription_data(self, cr, uid, ids, name, args, context=None):
@@ -607,15 +607,12 @@ class mail_thread(osv.AbstractModel):
if thread_id and hasattr(model_pool, 'message_update'):
model_pool.message_update(cr, user_id, [thread_id], msg, context=nosub_ctx)
else:
+ nosub_ctx = dict(nosub_ctx, mail_create_nolog=True)
thread_id = model_pool.message_new(cr, user_id, msg, custom_values, context=nosub_ctx)
else:
assert thread_id == 0, "Posting a message without model should be with a null res_id, to create a private message."
model_pool = self.pool.get('mail.thread')
- new_msg_id = model_pool.message_post_user_api(cr, uid, [thread_id], context=context, content_subtype='html', **msg)
-
- # when posting an incoming email to a document: subscribe the author, if a partner, as follower
- if model and thread_id and msg.get('author_id'):
- model_pool.message_subscribe(cr, uid, [thread_id], [msg.get('author_id')], context=context)
+ new_msg_id = model_pool.message_post(cr, uid, [thread_id], context=context, subtype='mail.mt_comment', **msg)
if partner_ids:
# postponed after message_post, because this is an external message and we don't want to create
@@ -760,7 +757,7 @@ class mail_thread(osv.AbstractModel):
_logger.debug('Parsing Message without message-id, generating a random one: %s', message_id)
msg_dict['message_id'] = message_id
- if 'Subject' in message:
+ if message.get('Subject'):
msg_dict['subject'] = decode(message.get('Subject'))
# Envelope fields not stored in mail.message but made available for message_new()
@@ -768,16 +765,15 @@ class mail_thread(osv.AbstractModel):
msg_dict['to'] = decode(message.get('to'))
msg_dict['cc'] = decode(message.get('cc'))
- if 'From' in message:
+ if message.get('From'):
author_ids = self._message_find_partners(cr, uid, message, ['From'], context=context)
if author_ids:
msg_dict['author_id'] = author_ids[0]
- else:
- msg_dict['email_from'] = decode(message.get('from'))
+ msg_dict['email_from'] = decode(message.get('from'))
partner_ids = self._message_find_partners(cr, uid, message, ['To', 'Cc'], context=context)
msg_dict['partner_ids'] = [(4, partner_id) for partner_id in partner_ids]
- if 'Date' in message:
+ if message.get('Date'):
try:
date_hdr = decode(message.get('Date'))
parsed_date = dateutil.parser.parse(date_hdr, fuzzy=True)
@@ -795,12 +791,12 @@ class mail_thread(osv.AbstractModel):
stored_date = datetime.datetime.now()
msg_dict['date'] = stored_date.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
- if 'In-Reply-To' in message:
+ if message.get('In-Reply-To'):
parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', '=', decode(message['In-Reply-To']))])
if parent_ids:
msg_dict['parent_id'] = parent_ids[0]
- if 'References' in message and 'parent_id' not in msg_dict:
+ if message.get('References') and 'parent_id' not in msg_dict:
parent_ids = self.pool.get('mail.message').search(cr, uid, [('message_id', 'in',
[x.strip() for x in decode(message['References']).split()])])
if parent_ids:
@@ -821,77 +817,151 @@ class mail_thread(osv.AbstractModel):
"now deprecated res.log.")
self.message_post(cr, uid, [id], message, context=context)
- def message_create_partners_from_emails(self, cr, uid, emails, context=None):
+ def _message_add_suggested_recipient(self, cr, uid, result, obj, partner=None, email=None, reason='', context=None):
+ """ Called by message_get_suggested_recipients, to add a suggested
+ recipient in the result dictionary. The form is :
+ partner_id, partner_name<partner_email> or partner_name, reason """
+ if email and not partner:
+ partner_info = self.message_get_partner_info_from_emails(cr, uid, [email], context=context)[0]
+ if partner_info.get('partner_id'):
+ partner = self.pool.get('res.partner').browse(cr, SUPERUSER_ID, [partner_info.get('partner_id')], context=context)[0]
+ if email and email in [val[1] for val in result[obj.id]]: # already existing email -> skip
+ return result
+ if partner and partner in obj.message_follower_ids: # recipient already in the followers -> skip
+ return result
+ if partner and partner in [val[0] for val in result[obj.id]]: # already existing partner ID -> skip
+ return result
+ if partner and partner.email: # complete profile: id, name <email>
+ result[obj.id].append((partner.id, '%s<%s>' % (partner.name, partner.email), reason))
+ elif partner: # incomplete profile: id, name
+ result[obj.id].append((partner.id, '%s' % (partner.name), reason))
+ else: # unknown partner, we are probably managing an email address
+ result[obj.id].append((False, email, reason))
+ return result
+
+ def message_get_suggested_recipients(self, cr, uid, ids, context=None):
+ """ Returns suggested recipients for ids. Those are a list of
+ tuple (partner_id, partner_name, reason), to be managed by Chatter. """
+ result = dict.fromkeys(ids, list())
+ if self._all_columns.get('user_id'):
+ for obj in self.browse(cr, SUPERUSER_ID, ids, context=context): # SUPERUSER because of a read on res.users that would crash otherwise
+ if not obj.user_id or not obj.user_id.partner_id:
+ continue
+ self._message_add_suggested_recipient(cr, uid, result, obj, partner=obj.user_id.partner_id, reason=self._all_columns['user_id'].column.string, context=context)
+ return result
+
+ def message_get_partner_info_from_emails(self, cr, uid, emails, link_mail=False, context=None):
""" Convert a list of emails into a list partner_ids and a list
new_partner_ids. The return value is non conventional because
it is meant to be used by the mail widget.
:return dict: partner_ids and new_partner_ids
"""
- partner_obj = self.pool.get('res.partner')
mail_message_obj = self.pool.get('mail.message')
-
- partner_ids = []
- new_partner_ids = []
+ partner_obj = self.pool.get('res.partner')
+ result = list()
for email in emails:
+ partner_info = {'full_name': email, 'partner_id': False}
m = re.search(r"((.+?)\s*<)?([^<>]+@[^<>]+)>?", email, re.IGNORECASE | re.DOTALL)
- name = m.group(2) or m.group(0)
- email = m.group(3)
- ids = partner_obj.search(cr, SUPERUSER_ID, [('email', '=', email)], context=context)
+ if not m:
+ continue
+ email_address = m.group(3)
+ ids = partner_obj.search(cr, SUPERUSER_ID, [('email', '=', email_address)], context=context)
if ids:
- partner_ids.append(ids[0])
- partner_id = ids[0]
- else:
- partner_id = partner_obj.create(cr, uid, {
- 'name': name or email,
- 'email': email,
- }, context=context)
- new_partner_ids.append(partner_id)
+ partner_info['partner_id'] = ids[0]
+ result.append(partner_info)
# link mail with this from mail to the new partner id
- message_ids = mail_message_obj.search(cr, SUPERUSER_ID, ['|', ('email_from', '=', email), ('email_from', 'ilike', '<%s>' % email), ('author_id', '=', False)], context=context)
- if message_ids:
- mail_message_obj.write(cr, SUPERUSER_ID, message_ids, {'email_from': None, 'author_id': partner_id}, context=context)
- return {
- 'partner_ids': partner_ids,
- 'new_partner_ids': new_partner_ids,
- }
+ if link_mail and ids:
+ message_ids = mail_message_obj.search(cr, SUPERUSER_ID, [
+ '|',
+ ('email_from', '=', email),
+ ('email_from', 'ilike', '<%s>' % email),
+ ('author_id', '=', False)
+ ], context=context)
+ if message_ids:
+ mail_message_obj.write(cr, SUPERUSER_ID, message_ids, {'author_id': ids[0]}, context=context)
+ return result
def message_post(self, cr, uid, thread_id, body='', subject=None, type='notification',
- subtype=None, parent_id=False, attachments=None, context=None, **kwargs):
+ subtype=None, parent_id=False, attachments=None, context=None,
+ content_subtype='html', **kwargs):
""" Post a new message in an existing thread, returning the new
- mail.message ID. Extra keyword arguments will be used as default
- column values for the new mail.message record.
- Auto link messages for same id and object
+ mail.message ID.
+
:param int thread_id: thread ID to post into, or list with one ID;
if False/0, mail.message model will also be set as False
:param str body: body of the message, usually raw HTML that will
be sanitized
- :param str subject: optional subject
- :param str type: mail_message.type
- :param int parent_id: optional ID of parent message in this thread
+ :param str type: see mail_message.type field
+ :param str content_subtype:: if plaintext: convert body into html
+ :param int parent_id: handle reply to a previous message by adding the
+ parent partners to the message in case of private discussion
:param tuple(str,str) attachments or list id: list of attachment tuples in the form
``(name,content)``, where content is NOT base64 encoded
- :return: ID of newly created mail.message
+
+ Extra keyword arguments will be used as default column values for the
+ new mail.message record. Special cases:
+ - attachment_ids: supposed not attached to any document; attach them
+ to the related document. Should only be set by Chatter.
+ :return int: ID of newly created mail.message
"""
if context is None:
context = {}
if attachments is None:
attachments = {}
+ mail_message = self.pool.get('mail.message')
+ ir_attachment = self.pool.get('ir.attachment')
- assert (not thread_id) or isinstance(thread_id, (int, long)) or \
- (isinstance(thread_id, (list, tuple)) and len(thread_id) == 1), "Invalid thread_id; should be 0, False, an ID or a list with one ID"
+ assert (not thread_id) or \
+ isinstance(thread_id, (int, long)) or \
+ (isinstance(thread_id, (list, tuple)) and len(thread_id) == 1), \
+ "Invalid thread_id; should be 0, False, an ID or a list with one ID"
if isinstance(thread_id, (list, tuple)):
- thread_id = thread_id and thread_id[0]
- mail_message = self.pool.get('mail.message')
+ thread_id = thread_id[0]
# if we're processing a message directly coming from the gateway, the destination model was
- # set in the context.
+ # set in the context.
model = False
if thread_id:
model = context.get('thread_model', self._name) if self._name == 'mail.thread' else self._name
- attachment_ids = kwargs.pop('attachment_ids', [])
+ # 1: Handle content subtype: if plaintext, converto into HTML
+ if content_subtype == 'plaintext':
+ body = tools.plaintext2html(body)
+
+ # 2: Private message: add recipients (recipients and author of parent message)
+ # + legacy-code management (! we manage only 4 and 6 commands)
+ partner_ids = set()
+ kwargs_partner_ids = kwargs.pop('partner_ids', [])
+ for partner_id in kwargs_partner_ids:
+ if isinstance(partner_id, (list, tuple)) and partner_id[0] == 4 and len(partner_id) == 2:
+ partner_ids.add(partner_id[1])
+ if isinstance(partner_id, (list, tuple)) and partner_id[0] == 6 and len(partner_id) == 3:
+ partner_ids |= set(partner_id[2])
+ elif isinstance(partner_id, (int, long)):
+ partner_ids.add(partner_id)
+ else:
+ pass # we do not manage anything else
+ if parent_id and model == 'mail.thread':
+ parent_message = mail_message.browse(cr, uid, parent_id, context=context)
+ partner_ids |= set([partner.id for partner in parent_message.partner_ids])
+ if parent_message.author_id:
+ partner_ids.add(parent_message.author_id.id)
+
+ # 3. Attachments
+ # - HACK TDE FIXME: Chatter: attachments linked to the document (not done JS-side), load the message
+ attachment_ids = kwargs.pop('attachment_ids', []) or [] # because we could receive None (some old code sends None)
+ if attachment_ids:
+ filtered_attachment_ids = ir_attachment.search(cr, SUPERUSER_ID, [
+ ('res_model', '=', 'mail.compose.message'),
+ ('res_id', '=', 0),
+ ('create_uid', '=', uid),
+ ('id', 'in', attachment_ids)], context=context)
+ if filtered_attachment_ids:
+ ir_attachment.write(cr, SUPERUSER_ID, filtered_attachment_ids, {'res_model': model, 'res_id': thread_id}, context=context)
+ attachment_ids = [(4, id) for id in attachment_ids]
+ # Handle attachments parameter, that is a dictionary of attachments
for name, content in attachments:
if isinstance(content, unicode):
content = content.encode('utf-8')
@@ -900,20 +970,25 @@ class mail_thread(osv.AbstractModel):
'datas': base64.b64encode(str(content)),
'datas_fname': name,
'description': name,
- 'res_model': context.get('thread_model') or self._name,
+ 'res_model': model,
'res_id': thread_id,
}
attachment_ids.append((0, 0, data_attach))
- # fetch subtype
+ # 4: mail.message.subtype
+ subtype_id = False
if subtype:
- s_data = subtype.split('.')
- if len(s_data) == 1:
- s_data = ('mail', s_data[0])
- ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, s_data[0], s_data[1])
+ if '.' not in subtype:
+ subtype = 'mail.%s' % subtype
+ ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, *subtype.split('.'))
subtype_id = ref and ref[1] or False
- else:
- subtype_id = False
+
+ # automatically subscribe recipients if asked to
+ if context.get('mail_post_autofollow') and thread_id and partner_ids:
+ partner_to_subscribe = partner_ids
+ if context.get('mail_post_autofollow_partner_ids'):
+ partner_to_subscribe = filter(lambda item: item in context.get('mail_post_autofollow_partner_ids'), partner_ids)
+ self.message_subscribe(cr, uid, [thread_id], list(partner_to_subscribe), context=context)
# _mail_flat_thread: automatically set free messages to the first posted message
if self._mail_flat_thread and not parent_id and thread_id:
@@ -941,86 +1016,34 @@ class mail_thread(osv.AbstractModel):
'parent_id': parent_id,
'attachment_ids': attachment_ids,
'subtype_id': subtype_id,
+ 'partner_ids': [(4, pid) for pid in partner_ids],
})
# Avoid warnings about non-existing fields
for x in ('from', 'to', 'cc'):
values.pop(x, None)
- return mail_message.create(cr, uid, values, context=context)
+ # Create and auto subscribe the author
+ msg_id = mail_message.create(cr, uid, values, context=context)
+ message = mail_message.browse(cr, uid, msg_id, context=context)
+ if message.author_id and thread_id and type != 'notification':
+ self.message_subscribe(cr, uid, [thread_id], [message.author_id.id], context=context)
+ return msg_id
+
+ #------------------------------------------------------
+ # Compatibility methods: do not use
+ # TDE TODO: remove me in 8.0
+ #------------------------------------------------------
+
+ def message_create_partners_from_emails(self, cr, uid, emails, context=None):
+ return {'partner_ids': [], 'new_partner_ids': []}
def message_post_user_api(self, cr, uid, thread_id, body='', parent_id=False,
attachment_ids=None, content_subtype='plaintext',
context=None, **kwargs):
- """ Wrapper on message_post, used for user input :
- - mail gateway
- - quick reply in Chatter (refer to mail.js), not
- the mail.compose.message wizard
- The purpose is to perform some pre- and post-processing:
- - if body is plaintext: convert it into html
- - if parent_id: handle reply to a previous message by adding the
- parent partners to the message
- - type and subtype: comment and mail.mt_comment by default
- - attachment_ids: supposed not attached to any document; attach them
- to the related document. Should only be set by Chatter.
- """
- mail_message_obj = self.pool.get('mail.message')
- ir_attachment = self.pool.get('ir.attachment')
-
- # 1.A.1: add recipients of parent message (# TDE FIXME HACK: mail.thread -> private message)
- partner_ids = set([])
- if parent_id and self._name == 'mail.thread':
- parent_message = mail_message_obj.browse(cr, uid, parent_id, context=context)
- partner_ids |= set([(4, partner.id) for partner in parent_message.partner_ids])
- if parent_message.author_id.id:
- partner_ids.add((4, parent_message.author_id.id))
-
- # 1.A.2: add specified recipients
- param_partner_ids = set()
- for item in kwargs.pop('partner_ids', []):
- if isinstance(item, (list)):
- param_partner_ids.add((item[0], item[1]))
- elif isinstance(item, (int, long)):
- param_partner_ids.add((4, item))
- else:
- param_partner_ids.add(item)
- partner_ids |= param_partner_ids
-
- # 1.A.3: add parameters recipients as follower
- # TDE FIXME in 7.1: should check whether this comes from email_list or partner_ids
- if param_partner_ids and self._name != 'mail.thread':
- self.message_subscribe(cr, uid, [thread_id], [pid[1] for pid in param_partner_ids], context=context)
-
- # 1.B: handle body, message_type and message_subtype
- if content_subtype == 'plaintext':
- body = tools.plaintext2html(body)
- msg_type = kwargs.pop('type', 'comment')
- msg_subtype = kwargs.pop('subtype', 'mail.mt_comment')
-
- # 2. Pre-processing: attachments
- # HACK TDE FIXME: Chatter: attachments linked to the document (not done JS-side), load the message
- if attachment_ids:
- # TDE FIXME (?): when posting a private message, we use mail.thread as a model
- # However, attaching doc to mail.thread is not possible, mail.thread does not have any table
- model = self._name
- if model == 'mail.thread':
- model = False
- filtered_attachment_ids = ir_attachment.search(cr, SUPERUSER_ID, [
- ('res_model', '=', 'mail.compose.message'),
- ('res_id', '=', 0),
- ('create_uid', '=', uid),
- ('id', 'in', attachment_ids)], context=context)
- if filtered_attachment_ids:
- if thread_id and model:
- ir_attachment.write(cr, SUPERUSER_ID, attachment_ids, {'res_model': model, 'res_id': thread_id}, context=context)
- else:
- attachment_ids = []
- attachment_ids = [(4, id) for id in attachment_ids]
-
- # 3. Post message
- return self.message_post(cr, uid, thread_id=thread_id, body=body,
- type=msg_type, subtype=msg_subtype, parent_id=parent_id,
- attachment_ids=attachment_ids, partner_ids=list(partner_ids), context=context, **kwargs)
+ return self.message_post(cr, uid, thread_id, body=body, parent_id=parent_id,
+ attachment_ids=attachment_ids, content_subtype=content_subtype,
+ context=context, **kwargs)
#------------------------------------------------------
# Followers API
@@ -1142,12 +1165,25 @@ class mail_thread(osv.AbstractModel):
# add followers coming from res.users relational fields that are tracked
user_ids = [getattr(record, name).id for name in user_field_lst if getattr(record, name)]
- for partner_id in [user.partner_id.id for user in self.pool.get('res.users').browse(cr, SUPERUSER_ID, user_ids, context=context)]:
+ user_id_partner_ids = [user.partner_id.id for user in self.pool.get('res.users').browse(cr, SUPERUSER_ID, user_ids, context=context)]
+ for partner_id in user_id_partner_ids:
new_followers.setdefault(partner_id, None)
for pid, subtypes in new_followers.items():
subtypes = list(subtypes) if subtypes is not None else None
self.message_subscribe(cr, uid, [record.id], [pid], subtypes, context=context)
+
+ # find first email message, set it as unread for auto_subscribe fields for them to have a notification
+ if user_id_partner_ids:
+ msg_ids = self.pool.get('mail.message').search(cr, uid, [
+ ('model', '=', self._name),
+ ('res_id', '=', record.id),
+ ('type', '=', 'email')], limit=1, context=context)
+ if not msg_ids and record.message_ids:
+ msg_ids = [record.message_ids[-1].id]
+ if msg_ids:
+ self.pool.get('mail.notification')._notify(cr, uid, msg_ids[0], partners_to_notify=user_id_partner_ids, context=context)
+
return True
#------------------------------------------------------
diff --git a/addons/mail/mail_thread_view.xml b/addons/mail/mail_thread_view.xml
index 6769fb0d063..75c86e4645d 100644
--- a/addons/mail/mail_thread_view.xml
+++ b/addons/mail/mail_thread_view.xml
@@ -6,7 +6,7 @@
<field name="tag">mail.wall</field>
<field name="res_model">mail.message</field>
<field name="context">{
- 'default_model': 'res.users',
+ 'default_model': 'res.users',
'default_res_id': uid,
}</field>
<field name="params" eval="&quot;{
@@ -34,8 +34,8 @@
<field name="tag">mail.wall</field>
<field name="res_model">mail.message</field>
<field name="context">{
- 'default_model': 'res.users',
- 'default_res_id': uid,
+ 'default_model': 'res.users',
+ 'default_res_id': uid,
'search_default_message_unread': True
}</field>
<field name="params" eval="&quot;{
@@ -44,7 +44,8 @@
],
'view_mailbox': True,
'read_action': 'read',
- 'show_compose_message': False }&quot;"/>
+ 'show_compose_message': False
+ }&quot;"/>
<field name="help" type="html">
<p>
<b>No private message.</b>
@@ -59,8 +60,8 @@
<field name="tag">mail.wall</field>
<field name="res_model">mail.message</field>
<field name="context">{
- 'default_model': 'res.users',
- 'default_res_id': uid,
+ 'default_model': 'res.users',
+ 'default_res_id': uid,
'search_default_message_unread': True
}</field>
<field name="params" eval="&quot;{
@@ -69,7 +70,8 @@
],
'view_mailbox': True,
'read_action': 'read',
- 'compose_as_todo': True }&quot;"/>
+ 'compose_as_todo': True
+ }&quot;"/>
<field name="help" type="html">
<p>
<b>No todo.</b>
@@ -84,7 +86,7 @@
<field name="name">Archives</field>
<field name="tag">mail.wall</field>
<field name="context">{
- 'default_model': 'res.users',
+ 'default_model': 'res.users',
'default_res_id': uid
}</field>
<field name="params" eval="&quot;{
@@ -94,7 +96,8 @@
('author_id.user_ids', 'in', [uid]),
],
'view_mailbox': True,
- 'show_compose_message': False }&quot;"/>
+ 'show_compose_message': False
+ }&quot;"/>
<field name="help" type="html">
<p>
No message found and no message sent yet.
diff --git a/addons/mail/res_partner.py b/addons/mail/res_partner.py
index 77f680bcab2..8527e60aba0 100644
--- a/addons/mail/res_partner.py
+++ b/addons/mail/res_partner.py
@@ -18,9 +18,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
-
+from openerp.tools.translate import _
from openerp.osv import fields, osv
+
class res_partner_mail(osv.Model):
""" Update partner to add a field about notification preferences """
_name = "res.partner"
@@ -29,19 +30,28 @@ class res_partner_mail(osv.Model):
_columns = {
'notification_email_send': fields.selection([
- ('all', 'All feeds'),
- ('comment', 'Comments and Emails'),
- ('email', 'Emails only'),
- ('none', 'Never')
- ], 'Receive Feeds by Email', required=True,
- help="Choose in which case you want to receive an email when you "\
- "receive new feeds."),
+ ('none', 'Never'),
+ ('email', 'Incoming Emails only'),
+ ('comment', 'Incoming Emails and Discussions'),
+ ('all', 'All Messages (discussions, emails, followed system notifications)'),
+ ], 'Receive Messages by Email', required=True,
+ help="Policy to receive emails for new messages pushed to your personal Inbox:\n"
+ "- Never: no emails are sent\n"
+ "- Incoming Emails only: for messages received by the system via email\n"
+ "- Incoming Emails and Discussions: for incoming emails along with internal discussions\n"
+ "- All Messages: for every notification you receive in your Inbox"),
}
_defaults = {
'notification_email_send': lambda *args: 'comment'
}
+ def message_get_suggested_recipients(self, cr, uid, ids, context=None):
+ recipients = super(res_partner_mail, self).message_get_suggested_recipients(cr, uid, ids, context=context)
+ for partner in self.browse(cr, uid, ids, context=context):
+ self._message_add_suggested_recipient(cr, uid, recipients, partner, partner=partner, reason=_('Partner Profile'))
+ return recipients
+
def message_post(self, cr, uid, thread_id, **kwargs):
""" Override related to res.partner. In case of email message, set it as
private:
diff --git a/addons/mail/res_partner_view.xml b/addons/mail/res_partner_view.xml
index a4aa56ae971..7df5d27c9d0 100644
--- a/addons/mail/res_partner_view.xml
+++ b/addons/mail/res_partner_view.xml
@@ -7,6 +7,9 @@
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
+ <xpath expr="//field[@name='active']" position="after">
+ <field name='notification_email_send'/>
+ </xpath>
<xpath expr="//sheet" position="after">
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
diff --git a/addons/mail/res_users.py b/addons/mail/res_users.py
index 41b0a46c2b4..f7c91939b98 100644
--- a/addons/mail/res_users.py
+++ b/addons/mail/res_users.py
@@ -113,13 +113,6 @@ class res_users(osv.Model):
thread_id = thread_id[0]
return self.browse(cr, SUPERUSER_ID, thread_id).partner_id.id
- def message_post_user_api(self, cr, uid, thread_id, context=None, **kwargs):
- """ Redirect the posting of message on res.users to the related partner.
- This is done because when giving the context of Chatter on the
- various mailboxes, we do not have access to the current partner_id. """
- partner_id = self._message_post_get_pid(cr, uid, thread_id, context=context)
- return self.pool.get('res.partner').message_post_user_api(cr, uid, partner_id, context=context, **kwargs)
-
def message_post(self, cr, uid, thread_id, context=None, **kwargs):
""" Redirect the posting of message on res.users to the related partner.
This is done because when giving the context of Chatter on the
@@ -139,6 +132,27 @@ class res_users(osv.Model):
self.pool.get('res.partner').message_subscribe(cr, uid, [partner_id], partner_ids, subtype_ids=subtype_ids, context=context)
return True
+ def message_get_partner_info_from_emails(self, cr, uid, emails, link_mail=False, context=None):
+ return self.pool.get('res.partner').message_get_partner_info_from_emails(cr, uid, emails, link_mail=link_mail, context=context)
+
+ def message_get_suggested_recipients(self, cr, uid, ids, context=None):
+ partner_ids = []
+ for id in ids:
+ partner_ids.append(self.browse(cr, SUPERUSER_ID, id).partner_id.id)
+ return self.pool.get('res.partner').message_get_suggested_recipients(cr, uid, partner_ids, context=context)
+
+ #------------------------------------------------------
+ # Compatibility methods: do not use
+ # TDE TODO: remove me in 8.0
+ #------------------------------------------------------
+
+ def message_post_user_api(self, cr, uid, thread_id, context=None, **kwargs):
+ """ Redirect the posting of message on res.users to the related partner.
+ This is done because when giving the context of Chatter on the
+ various mailboxes, we do not have access to the current partner_id. """
+ partner_id = self._message_post_get_pid(cr, uid, thread_id, context=context)
+ return self.pool.get('res.partner').message_post_user_api(cr, uid, partner_id, context=context, **kwargs)
+
def message_create_partners_from_emails(self, cr, uid, emails, context=None):
return self.pool.get('res.partner').message_create_partners_from_emails(cr, uid, emails, context=context)
diff --git a/addons/mail/static/src/css/mail.css b/addons/mail/static/src/css/mail.css
index b7de1801925..51306727a09 100644
--- a/addons/mail/static/src/css/mail.css
+++ b/addons/mail/static/src/css/mail.css
@@ -54,6 +54,9 @@
min-height: 42px;
border: solid 1px rgba(0,0,0,0.03);
}
+.openerp .oe_mail .oe_msg.oe_msg_nobody{
+ background: #F8F8F8;
+}
.openerp .oe_mail .oe_msg .oe_msg_left{
position: absolute;
left:0; top: 0; bottom: 0; width: 40px;
@@ -224,20 +227,30 @@
height: 24px;
width: 100%;
}
+.openerp .oe_mail .oe_msg.oe_msg_composer_compact .oe_sep_word{
+ margin-right: 8px;
+ margin-left: 8px;
+}
.openerp .oe_mail .oe_msg.oe_msg_composer_compact .oe_compact{
height: 24px;
width: 100%;
padding: 2px 4px;
+ color: #AAA;
+ cursor: text;
+}
+.openerp .oe_mail .oe_msg.oe_msg_composer_compact .oe_compact_record {
+ font-size: 13px;
+ font-style: bold;
+ text-align: center;
+}
+.openerp .oe_mail .oe_msg.oe_msg_composer_compact .oe_compact_inbox {
border: 1px solid #CCC;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
background: white;
font-size: 14px;
- color: #AAA;
font-style: italic;
- word-spacing: 3px;
- cursor: text;
}
/* d) I.E. tweaks for Message action icons */
@@ -269,20 +282,20 @@
margin-top: 4px;
margin-bottom: 4px;
}
-.openerp .oe_mail .oe_msg_composer .oe_msg_attachment_list{
+.openerp .oe_mail .oe_msg_composer .oe_msg_attachment_list {
display: block;
}
-.openerp .oe_mail .oe_msg_composer .oe_emails_from{
+.openerp .oe_mail .oe_msg_composer .oe_recipients {
font-size: 12px;
margin-left: 20px;
margin-bottom: 2px;
}
-.openerp .oe_mail .oe_msg_composer .oe_emails_from label{
+.openerp .oe_mail .oe_msg_composer .oe_recipients label{
vertical-align: middle;
display: block;
line-height: 14px;
}
-.openerp .oe_mail .oe_msg_composer .oe_emails_from input{
+.openerp .oe_mail .oe_msg_composer .oe_recipients input{
vertical-align: middle;
}
.openerp .oe_mail .oe_attachment{
@@ -439,7 +452,8 @@
line-height: 12px;
vertical-align: middle;
}
-.openerp .oe_mail .oe_msg_footer button.oe_post{
+.openerp .oe_mail .oe_msg_footer button.oe_post,
+.openerp .oe_mail .oe_msg_footer button.oe_log{
position: relative;
z-index: 2;
}
@@ -458,18 +472,20 @@
.openerp .oe_mail .oe_hidden_input_file, .openerp .oe_mail .oe_hidden_input_file form{
display:inline;
}
-.openerp .oe_mail .oe_msg_footer button.oe_full{
- width:24px;
- overflow:hidden;
+.openerp .oe_mail .oe_msg_center button.oe_full{
+ width: 24px;
+ height: 22px;
+ overflow: hidden;
float: right;
- filter:none;
+ filter: none;
}
-.openerp .oe_mail .oe_msg_footer button.oe_full .oe_e{
+.openerp .oe_mail .oe_msg_center button.oe_full .oe_e{
position: relative;
- top: -4px;
- margin-left: -9px;
- vertical-align: middle;
- filter:none;
+ top: -9px;
+ margin-left: -5px;
+ vertical-align: top;
+ filter: none;
+ height: 14px;
}
.openerp .oe_mail button.oe_attach, .openerp .oe_mail button.oe_full{
background: transparent;
diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js
index e5c8f40eb2e..f6e098b466d 100644
--- a/addons/mail/static/src/js/mail.js
+++ b/addons/mail/static/src/js/mail.js
@@ -21,6 +21,19 @@ openerp.mail = function (session) {
mail.ChatterUtils = {
+ /** parse text to find email: Tagada <address@mail.fr> -> [Tagada, address@mail.fr] or False */
+ parse_email: function (text) {
+ var result = text.match(/(.*)<(.*@.*)>/);
+ if (result) {
+ return [_.str.trim(result[1]), _.str.trim(result[2])];
+ }
+ result = text.match(/(.*@.*)/);
+ if (result) {
+ return [_.str.trim(result[1]), _.str.trim(result[1])];
+ }
+ return [text, false];
+ },
+
/* Get an image in /web/binary/image?... */
get_image: function (session, model, field, id, resize) {
var r = resize ? encodeURIComponent(resize) : '';
@@ -255,11 +268,15 @@ openerp.mail = function (session) {
this.avatar = mail.ChatterUtils.get_image(this.session, 'res.users', 'image_small', this.session.uid);
}
if (this.author_id && this.author_id[1]) {
- var email = this.author_id[1].match(/(.*)<(.*@.*)>/);
- if (!email) {
- this.author_id.push(_.str.escapeHTML(this.author_id[1]), '', this.author_id[1]);
- } else {
- this.author_id.push(_.str.escapeHTML(email[0]), _.str.trim(email[1]), email[2]);
+ var parsed_email = mail.ChatterUtils.parse_email(this.author_id[1]);
+ this.author_id.push(parsed_email[0], parsed_email[1]);
+ }
+ if (this.partner_ids && this.partner_ids.length > 3) {
+ this.extra_partners_nbr = this.partner_ids.length - 3;
+ this.extra_partners_str = ''
+ var extra_partners = this.partner_ids.slice(3);
+ for (var key in extra_partners) {
+ this.extra_partners_str += extra_partners[key][1];
}
}
},
@@ -371,14 +388,24 @@ openerp.mail = function (session) {
* @param {Object} [context] context passed to the
* mail.compose.message DataSetSearch. Please refer to this model
* for more details about fields and default values.
+ * @param {Object} recipients = [
+ {
+ 'email_address': [str],
+ 'partner_id': False/[int],
+ 'name': [str],
+ 'full_name': name<email_address>,
+ },
+ { ... },
+ ]
*/
init: function (parent, datasets, options) {
this._super(parent, datasets, options);
this.show_compact_message = false;
this.show_delete_attachment = true;
- this.emails_from = [];
- this.partners_from = [];
+ this.is_log = false;
+ this.recipients = [];
+ this.recipient_ids = [];
},
start: function () {
@@ -479,15 +506,13 @@ openerp.mail = function (session) {
bind_events: function () {
var self = this;
-
- this.$('.oe_compact').on('click', _.bind( this.on_compose_expandable, this));
-
- // set the function called when attachments are added
- this.$('input.oe_form_binary_file').on('change', _.bind( this.on_attachment_change, this) );
-
- this.$('.oe_cancel').on('click', _.bind( this.on_cancel, this) );
- this.$('.oe_post').on('click', _.bind( this.on_message_post, this) );
- this.$('.oe_full').on('click', _.bind( this.on_compose_fullmail, this, this.id ? 'reply' : 'comment') );
+ this.$('.oe_compact_inbox').on('click', self.on_toggle_quick_composer);
+ this.$('.oe_compose_post').on('click', self.on_toggle_quick_composer);
+ this.$('.oe_compose_log').on('click', self.on_toggle_quick_composer);
+ this.$('input.oe_form_binary_file').on('change', _.bind( this.on_attachment_change, this));
+ this.$('.oe_cancel').on('click', _.bind( this.on_cancel, this));
+ this.$('.oe_post').on('click', self.on_message_post);
+ this.$('.oe_full').on('click', _.bind( this.on_compose_fullmail, this, this.id ? 'reply' : 'comment'));
/* stack for don't close the compose form if the user click on a button */
this.$('.oe_msg_left, .oe_msg_center').on('mousedown', _.bind( function () { this.stay_open = true; }, this));
this.$('.oe_msg_left, .oe_msg_content').on('mouseup', _.bind( function () { this.$('textarea').focus(); }, this));
@@ -497,13 +522,12 @@ openerp.mail = function (session) {
this.$('textarea').autosize();
// auto close
- this.$('textarea').on('blur', _.bind( this.on_compose_expandable, this));
+ this.$('textarea').on('blur', self.on_toggle_quick_composer);
// event: delete child attachments off the oe_msg_attachment_list box
this.$(".oe_msg_attachment_list").on('click', '.oe_delete', this.on_attachment_delete);
- this.$(".oe_emails_from").on('change', 'input', this.on_checked_email_from);
- this.$(".oe_partners_from").on('change', 'input', this.on_checked_partner_from);
+ this.$(".oe_recipients").on('change', 'input', this.on_checked_recipient);
},
on_compose_fullmail: function (default_composition_mode) {
@@ -511,16 +535,26 @@ openerp.mail = function (session) {
if(!this.do_check_attachment_upload()) {
return false;
}
-
- // create list of new partners
- this.check_recipient_partners().done(function (partner_ids) {
+ var recipient_done = $.Deferred();
+ if (this.is_log) {
+ recipient_done.resolve([]);
+ }
+ else {
+ recipient_done = this.check_recipient_partners();
+ }
+ $.when(recipient_done).done(function (partner_ids) {
var context = {
'default_composition_mode': default_composition_mode,
'default_parent_id': self.id,
'default_body': mail.ChatterUtils.get_text2html(self.$el ? (self.$el.find('textarea:not(.oe_compact)').val() || '') : ''),
'default_attachment_ids': _.map(self.attachment_ids, function (file) {return file.id;}),
'default_partner_ids': partner_ids,
+ 'mail_post_autofollow': true,
+ 'mail_post_autofollow_partner_ids': partner_ids,
};
+ if (self.is_log) {
+ _.extend(context, {'mail_compose_log': true});
+ }
if (default_composition_mode != 'reply' && self.context.default_model && self.context.default_res_id) {
context.default_model = self.context.default_model;
context.default_res_id = self.context.default_res_id;
@@ -573,68 +607,122 @@ openerp.mail = function (session) {
check_recipient_partners: function () {
var self = this;
- var partners_from = [];
- var emails = [];
- _.each(this.emails_from, function (email_from) {
- if (email_from[1] && !_.find(emails, function (email) {return email == email_from[0][4];})) {
- emails.push(email_from[0][1]);
- }
- });
- var deferred_check = $.Deferred();
- if (emails.length == 0) {
- return deferred_check.resolve(partners_from);
- }
- self.parent_thread.ds_thread._model.call('message_create_partners_from_emails', [emails]).then(function (partners) {
- partners_from = _.clone(partners.partner_ids);
- var deferreds = [];
- _.each(partners.new_partner_ids, function (id) {
+ var check_done = $.Deferred();
+ var recipients = _.filter(this.recipients, function (recipient) { return recipient.checked });
+ var recipients_to_find = _.filter(recipients, function (recipient) { return (! recipient.partner_id) });
+ var names_to_find = _.pluck(recipients_to_find, 'full_name');
+ var recipients_to_check = _.filter(recipients, function (recipient) { return (recipient.partner_id && ! recipient.email_address) });
+ var recipient_ids = _.pluck(_.filter(recipients, function (recipient) { return recipient.partner_id && recipient.email_address }), 'partner_id');
+ var names_to_remove = [];
+ var recipient_ids_to_remove = [];
+
+ // have unknown names -> call message_get_partner_info_from_emails to try to find partner_id
+ var find_done = $.Deferred();
+ if (names_to_find.length > 0) {
+ find_done = self.parent_thread.ds_thread._model.call('message_get_partner_info_from_emails', [names_to_find]);
+ }
+ else {
+ find_done.resolve([]);
+ }
+
+ // for unknown names + incomplete partners -> open popup - cancel = remove from recipients
+ $.when(find_done).pipe(function (result) {
+ var emails_deferred = [];
+ var recipient_popups = result.concat(recipients_to_check);
+
+ _.each(recipient_popups, function (partner_info) {
var deferred = $.Deferred()
- deferreds.push(deferred);
- var pop = new session.web.form.FormOpenPopup(this);
+ emails_deferred.push(deferred);
+
+ var partner_name = partner_info.full_name;
+ var partner_id = partner_info.partner_id;
+ var parsed_email = mail.ChatterUtils.parse_email(partner_name);
+
+ var pop = new session.web.form.FormOpenPopup(this);
pop.show_element(
'res.partner',
- id,
- {
- 'force_email': true,
+ partner_id,
+ { 'force_email': true,
'ref': "compound_context",
- },
- {
+ 'default_name': parsed_email[0],
+ 'default_email': parsed_email[1],
+ }, {
title: _t("Please complete partner's informations"),
}
);
pop.on('closed', self, function () {
deferred.resolve();
});
- partners_from.push(id);
+ pop.view_form.on('on_button_cancel', self, function () {
+ names_to_remove.push(partner_name);
+ if (partner_id) {
+ recipient_ids_to_remove.push(partner_id);
+ }
+ });
});
- $.when.apply( $, deferreds ).then(function () {
- deferred_check.resolve(partners_from);
+
+ $.when.apply($, emails_deferred).then(function () {
+ var new_names_to_find = _.difference(names_to_find, names_to_remove);
+ find_done = $.Deferred();
+ if (new_names_to_find.length > 0) {
+ find_done = self.parent_thread.ds_thread._model.call('message_get_partner_info_from_emails', [new_names_to_find, true]);
+ }
+ else {
+ find_done.resolve([]);
+ }
+ $.when(find_done).pipe(function (result) {
+ var recipient_popups = result.concat(recipients_to_check);
+ _.each(recipient_popups, function (partner_info) {
+ if (partner_info.partner_id && _.indexOf(partner_info.partner_id, recipient_ids_to_remove) == -1) {
+ recipient_ids.push(partner_info.partner_id);
+ }
+ });
+ }).pipe(function () {
+ check_done.resolve(recipient_ids);
+ });
});
});
- return deferred_check;
+
+ return check_done;
},
on_message_post: function (event) {
var self = this;
if (this.do_check_attachment_upload() && (this.attachment_ids.length || this.$('textarea').val().match(/\S+/))) {
- // create list of new partners
- this.check_recipient_partners().done(function (partner_ids) {
- self.do_send_message_post(partner_ids);
- });
+ if (this.is_log) {
+ this.do_send_message_post([], this.is_log);
+ }
+ else {
+ this.check_recipient_partners().done(function (partner_ids) {
+ self.do_send_message_post(partner_ids, self.is_log);
+ });
+ }
}
},
- /*do post a message and fetch the message*/
- do_send_message_post: function (partner_ids) {
+ /* do post a message and fetch the message */
+ do_send_message_post: function (partner_ids, log) {
var self = this;
- this.parent_thread.ds_thread._model.call('message_post_user_api', [this.context.default_res_id], {
+ var values = {
'body': this.$('textarea').val(),
'subject': false,
'parent_id': this.context.default_parent_id,
'attachment_ids': _.map(this.attachment_ids, function (file) {return file.id;}),
'partner_ids': partner_ids,
- 'context': this.parent_thread.context,
- }).done(function (message_id) {
+ 'context': _.extend(this.parent_thread.context, {
+ 'mail_post_autofollow': true,
+ 'mail_post_autofollow_partner_ids': partner_ids,
+ }),
+ 'type': 'comment',
+ 'content_subtype': 'plaintext',
+ };
+ if (log) {
+ values['subtype'] = false;
+ }
+ else {
+ values['subtype'] = 'mail.mt_comment';
+ }
+ this.parent_thread.ds_thread._model.call('message_post', [this.context.default_res_id], values).done(function (message_id) {
var thread = self.parent_thread;
var root = thread == self.options.root_thread;
if (self.options.display_indented_thread < self.thread_level && thread.parent_message) {
@@ -650,18 +738,55 @@ openerp.mail = function (session) {
});
},
- /* convert the compact mode into the compose message
- */
- on_compose_expandable: function (event) {
- this.get_emails_from();
- if ((!this.stay_open || (event && event.type == 'click')) && (!this.show_composer || !this.$('textarea:not(.oe_compact)').val().match(/\S+/) && !this.attachment_ids.length)) {
- this.show_composer = !this.show_composer || this.stay_open;
- this.reinit();
+ /* Quick composer: toggle minimal / expanded mode
+ * - toggle minimal (one-liner) / expanded (textarea, buttons) mode
+ * - when going into expanded mode:
+ * - call `message_get_suggested_recipients` to have a list of partners to add
+ * - compute email_from list (list of unknown email_from to propose to create partners)
+ */
+ on_toggle_quick_composer: function (event) {
+ var self = this;
+ var $input = $(event.target);
+ this.compute_emails_from();
+ var email_addresses = _.pluck(this.recipients, 'email_address');
+ var suggested_partners = $.Deferred();
+
+ // if clicked: call for suggested recipients
+ if (event.type == 'click') {
+ this.is_log = $input.hasClass('oe_compose_log');
+ suggested_partners = this.parent_thread.ds_thread.call('message_get_suggested_recipients', [[this.context.default_res_id]]).done(function (additional_recipients) {
+ var thread_recipients = additional_recipients[self.context.default_res_id];
+ _.each(thread_recipients, function (recipient) {
+ var parsed_email = mail.ChatterUtils.parse_email(recipient[1]);
+ if (_.indexOf(email_addresses, parsed_email[1]) == -1) {
+ self.recipients.push({
+ 'checked': true,
+ 'partner_id': recipient[0],
+ 'full_name': recipient[1],
+ 'name': parsed_email[0],
+ 'email_address': parsed_email[1],
+ 'reason': recipient[2],
+ })
+ }
+ });
+ });
}
- if (!this.stay_open && this.show_composer && (!event || event.type != 'blur')) {
- this.$('textarea:not(.oe_compact):first').focus();
+ else {
+ suggested_partners.resolve({});
}
- return true;
+
+ // when call for suggested partners finished: re-render the widget
+ $.when(suggested_partners).pipe(function (additional_recipients) {
+ if ((!self.stay_open || (event && event.type == 'click')) && (!self.show_composer || !self.$('textarea:not(.oe_compact)').val().match(/\S+/) && !self.attachment_ids.length)) {
+ self.show_composer = !self.show_composer || self.stay_open;
+ self.reinit();
+ }
+ if (!self.stay_open && self.show_composer && (!event || event.type != 'blur')) {
+ self.$('textarea:not(.oe_compact):first').focus();
+ }
+ });
+
+ return suggested_partners;
},
do_hide_compact: function () {
@@ -678,7 +803,10 @@ openerp.mail = function (session) {
}
},
- get_emails_from: function () {
+ /** Compute the list of unknown email_from the the given thread
+ * TDE FIXME: seems odd to delegate to the composer
+ * TDE TODO: please de-obfuscate and comment your code */
+ compute_emails_from: function () {
var self = this;
var messages = [];
@@ -691,22 +819,28 @@ openerp.mail = function (session) {
// get all wall messages if is not a mail.Wall
_.each(this.options.root_thread.messages, function (msg) {messages.push(msg); messages.concat(msg.get_childs());});
}
-
+
_.each(messages, function (thread) {
if (thread.author_id && !thread.author_id[0] &&
- !_.find(self.emails_from, function (from) {return from[0][4] == thread.author_id[4];})) {
- self.emails_from.push([thread.author_id, true]);
+ !_.find(self.recipients, function (recipient) {return recipient.email_address == thread.author_id[3];})) {
+ self.recipients.push({ 'full_name': thread.author_id[1],
+ 'name': thread.author_id[2],
+ 'email_address': thread.author_id[3],
+ 'partner_id': false,
+ 'checked': true,
+ 'reason': 'Incoming email author'
+ });
}
});
- return self.emails_from;
+ return self.recipients;
},
- on_checked_email_from: function (event) {
+ on_checked_recipient: function (event) {
var $input = $(event.target);
var email = $input.attr("data");
- _.each(this.emails_from, function (email_from) {
- if (email_from[0][4] == email) {
- email_from[1] = $input.is(":checked");
+ _.each(this.recipients, function (recipient) {
+ if (recipient.email_address == email) {
+ recipient.checked = $input.is(":checked");
}
});
},
@@ -1236,12 +1370,12 @@ openerp.mail = function (session) {
/**
*If compose_message doesn't exist, instantiate the compose message.
- * Call the on_compose_expandable method to allow the user to write his message.
+ * Call the on_toggle_quick_composer method to allow the user to write his message.
* (Is call when a user click on "Reply" button)
*/
on_compose_message: function (event) {
this.instantiate_compose_message();
- this.compose_message.on_compose_expandable(event);
+ this.compose_message.on_toggle_quick_composer(event);
return false;
},
@@ -1810,7 +1944,7 @@ openerp.mail = function (session) {
};
session.client.action_manager.do_action(action);
});
- this.$(".oe_write_onwall").click(function(){ self.root.thread.on_compose_message(); });
+ this.$(".oe_write_onwall").click(function (event) { self.root.thread.on_compose_message(event); });
}
});
diff --git a/addons/mail/static/src/js/mail_followers.js b/addons/mail/static/src/js/mail_followers.js
index fba5eefc110..6fc2f0fdfcd 100644
--- a/addons/mail/static/src/js/mail_followers.js
+++ b/addons/mail/static/src/js/mail_followers.js
@@ -106,9 +106,12 @@ openerp_mail_followers = function(session, mail) {
on_remove_follower: function (event) {
var partner_id = $(event.target).data('id');
- var context = new session.web.CompoundContext(this.build_context(), {});
- return this.ds_model.call('message_unsubscribe', [[this.view.datarecord.id], [partner_id], context])
- .then(this.proxy('read_value'));
+ var name = $(event.target).parent().find("a").html();
+ if (confirm(_.str.sprintf(_t("Warning! \n %s won't be notified of any email or discussion on this document. Do you really want to remove him from the followers ?"), name))) {
+ var context = new session.web.CompoundContext(this.build_context(), {});
+ return this.ds_model.call('message_unsubscribe', [[this.view.datarecord.id], [partner_id], context])
+ .then(this.proxy('read_value'));
+ }
},
read_value: function () {
@@ -241,12 +244,15 @@ openerp_mail_followers = function(session, mail) {
},
do_unfollow: function () {
- _(this.$('.oe_msg_subtype_check')).each(function (record) {
- $(record).attr('checked',false);
- });
- var context = new session.web.CompoundContext(this.build_context(), {});
- return this.ds_model.call('message_unsubscribe_users', [[this.view.datarecord.id], [this.session.uid], context])
- .then(this.proxy('read_value'));
+ if (confirm(_t("Warning! \nYou won't be notified of any email or discussion on this document. Do you really want to unfollow this document ?"))) {
+ _(this.$('.oe_msg_subtype_check')).each(function (record) {
+ $(record).attr('checked',false);
+ });
+ var context = new session.web.CompoundContext(this.build_context(), {});
+ return this.ds_model.call('message_unsubscribe_users', [[this.view.datarecord.id], [this.session.uid], context])
+ .then(this.proxy('read_value'));
+ }
+ return false;
},
do_update_subscription: function (event) {
@@ -260,7 +266,9 @@ openerp_mail_followers = function(session, mail) {
});
if (!checklist.length) {
- this.do_unfollow();
+ if (!this.do_unfollow()) {
+ $(event.target).attr("checked", "checked");
+ }
} else {
var context = new session.web.CompoundContext(this.build_context(), {});
return this.ds_model.call('message_subscribe_users', [[this.view.datarecord.id], [this.session.uid], checklist, context])
diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml
index 4693adeb1ee..b26ef06f5e4 100644
--- a/addons/mail/static/src/xml/mail.xml
+++ b/addons/mail/static/src/xml/mail.xml
@@ -26,24 +26,35 @@
</div>
<div class="oe_msg_center">
<div class="oe_msg_content">
+ <button class="oe_full" title="Open the full mail composer"><span class='oe_e'>&amp;ograve</span></button>
<t t-call="mail.thread.list_recipients"/>
<textarea class="field_text"></textarea>
</div>
<div class="oe_msg_footer">
<div class="oe_msg_attachment_list"></div>
- <button class="oe_post">Post</button>
+ <button class="oe_post">
+ <t t-if="!widget.is_log">Send</t>
+ <t t-if="widget.is_log">Log a note</t>
+ </button>
<t t-call="mail.compose_message.add_attachment"/>
- <!--<a class="oe_cancel oe_e">X</a>-->
- <button class="oe_full" title="Open the full mail composer"><span class='oe_e'>&amp;ograve</span></button>
</div>
</div>
</div>
<div t-if="widget.show_compact_message and !widget.show_composer and !widget.options.readonly" t-attf-class="oe_msg oe_msg_composer_compact #{widget.thread_level and widget.options.display_indented_thread > -1 ? 'oe_msg_indented' : ''}">
- <div class="field_text oe_compact">
- <t t-if="widget.options.compose_placeholder" t-raw="widget.options.compose_placeholder"/>
- <t t-if="!widget.options.compose_placeholder and !widget.options.view_mailbox">Write to the followers of this document...</t>
- <t t-if="!widget.options.compose_placeholder and widget.options.view_mailbox">Share with my followers...</t>
- </div>
+ <t t-if="widget.options.view_mailbox">
+ <div class="field_text oe_compact oe_compact_inbox">
+ <t t-if="widget.options.compose_placeholder" t-raw="widget.options.compose_placeholder"/>
+ <t t-if="!widget.options.compose_placeholder and widget.options.view_mailbox">Share with my followers...</t>
+ </div>
+ </t>
+ <t t-if="!widget.options.view_mailbox">
+ <div class="field_text oe_compact oe_compact_record">
+ <a class="oe_compose_post" t-if="widget.options.compose_placeholder"><t t-raw="widget.options.compose_placeholder"/></a>
+ <a class="oe_compose_post" t-if="!widget.options.compose_placeholder and !widget.options.view_mailbox">Send a message</a>
+ <span class="oe_grey oe_sep_word">or</span>
+ <a class="oe_compose_log">Log a note</a>
+ </div>
+ </t>
</div>
<span t-if="!(widget.show_compact_message and !widget.show_composer) and !widget.show_composer" class="oe_placeholder_compose"></span>
</t>
@@ -57,10 +68,10 @@
<span class="oe_add">
<!-- uploader of file -->
<button class="oe_attach"><span class="oe_e">'</span></button>
- <span class='oe_attach_label'>File</span>
+ <span class='oe_attach_label'>Attach a File</span>
<t t-call="HiddenInputFile">
<t t-set="fileupload_id" t-value="widget.fileupload_id"/>
- <t t-set="fileupload_action">/web/binary/upload_attachment</t>
+ <t t-set="fileupload_action" t-translation="off">/web/binary/upload_attachment</t>
<input type="hidden" name="model" value="mail.compose.message"/>
<input type="hidden" name="id" value="0"/>
<input type="hidden" name="session_id" t-att-value="widget.session.session_id"/>
@@ -106,7 +117,7 @@
template to the recipients list
-->
<t t-name="mail.thread.list_recipients">
- <div class="oe_mail_list_recipients">
+ <div class="oe_mail_list_recipients" t-if="!widget.is_log">
To:
<t t-if="!widget.is_private">
<span class="oe_all_follower">
@@ -129,14 +140,17 @@
<a class="oe_more_hidden">&lt;&lt;&lt;</a>
</t>
</div>
- <div class="oe_emails_from">
- <t t-foreach='widget.emails_from' t-as='email_from'>
- <label title="Add them into recipients and followers">
- <input type="checkbox" t-att-checked="email_from[1] ? 'checked' : undefind" t-att-data="email_from[0][4]"/>
- <t t-raw="email_from[0][2]"/>
+ <div class="oe_recipients" t-if="!widget.is_log">
+ <t t-foreach='widget.recipients' t-as='recipient'>
+ <label t-attf-title="Add as recipient and follower (reason: #{recipient.reason})">
+ <input type="checkbox" t-att-checked="recipient.checked ? 'checked' : undefined" t-att-data="recipient.email_address"/>
+ <t t-raw="recipient.name"/> (<t t-raw="recipient.email_address"/>)
</label>
</t>
</div>
+ <div t-if="widget.is_log">
+ <span>Attach a note that will not be sent to the followers</span>
+ </div>
</t>
<!--
@@ -212,7 +226,7 @@
<!-- default layout -->
<t t-name="mail.thread.message">
- <div t-attf-class="oe_msg #{widget.thread_level and widget.options.display_indented_thread > -1 ? 'oe_msg_indented' : ''} oe_msg_#{widget.type}">
+ <div t-attf-class="oe_msg #{widget.thread_level and widget.options.display_indented_thread > -1 ? 'oe_msg_indented' : ''} #{widget.partner_ids.length == 0 ? 'oe_msg_nobody' : ''} oe_msg_#{widget.type}">
<div class='oe_msg_left'>
<a t-if="widget.options.show_link" t-attf-href="#model=res.partner&amp;id=#{widget.author_id[0]}" t-att-title="widget.author_id[1]">
@@ -245,6 +259,22 @@
</t>
<a t-if="widget.author_id and widget.options.show_link and widget.author_id[0]" t-attf-href="#model=res.partner&amp;id=#{widget.author_id[0]}"><t t-raw="widget.author_id[2]"/></a>
<span t-if="widget.author_id and (!widget.options.show_link or !widget.author_id[0])"><t t-raw="widget.author_id[2]"/></span>
+ <t t-if="widget.partner_ids.length == 0">
+ logged a note
+ </t>
+ <t t-if="widget.partner_ids.length > 0">
+ to
+ </t>
+ <t t-foreach="widget.partner_ids.slice(0, 3)" t-as="partner">
+ <span t-attf-class="oe_partner_follower">
+ <a t-if="widget.options.show_link" t-attf-href="#model=res.partner&amp;id=#{partner[0]}"><t t-raw="partner[1]"/></a>
+ <t t-if="!widget.options.show_link" t-raw="partner[1]"/>
+ </span>
+ <t t-if="!partner_last">,</t>
+ </t>
+ <t t-if="widget.partner_ids.length > 3">
+ <span t-att-title="widget.extra_partners_str">and <t t-raw="widget.extra_partners_nbr"/> more</span>
+ </t>
<span class='oe_subtle'>•</span>
<span t-att-title="widget.date"><t t-if="widget.timerelative" t-raw="widget.timerelative"/><t t-if="!widget.timerelative" t-raw="widget.date"/></span>
<span t-if="!widget.options.readonly" class='oe_subtle'>•</span>
diff --git a/addons/mail/tests/test_mail_features.py b/addons/mail/tests/test_mail_features.py
index 3d1ccdd34e4..e4f340fef98 100644
--- a/addons/mail/tests/test_mail_features.py
+++ b/addons/mail/tests/test_mail_features.py
@@ -102,18 +102,18 @@ class test_mail(TestMailBase):
# Previously-created group can be emailed now - it should have an implicit alias group+frogs@...
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
group_messages = frog_group.message_ids
- self.assertTrue(len(group_messages) == 2, 'New group should only have the original message + creation log')
+ self.assertTrue(len(group_messages) == 1, 'New group should only have the original message')
mail_frog_news = MAIL_TEMPLATE.format(to='Friendly Frogs <group+frogs@example.com>', subject='news', extra='')
self.mail_thread.message_process(cr, uid, None, mail_frog_news)
frog_group.refresh()
- self.assertTrue(len(frog_group.message_ids) == 3, 'Group should contain 3 messages now')
+ self.assertTrue(len(frog_group.message_ids) == 2, 'Group should contain 2 messages now')
# Even with a wrong destination, a reply should end up in the correct thread
mail_reply = MAIL_TEMPLATE.format(to='erroneous@example.com>', subject='Re: news',
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
self.mail_thread.message_process(cr, uid, None, mail_reply)
frog_group.refresh()
- self.assertTrue(len(frog_group.message_ids) == 4, 'Group should contain 4 messages now')
+ self.assertTrue(len(frog_group.message_ids) == 3, 'Group should contain 3 messages now')
# No model passed and no matching alias must raise
mail_spam = MAIL_TEMPLATE.format(to='noone@example.com', subject='spam', extra='')
@@ -137,7 +137,7 @@ class test_mail(TestMailBase):
new_mail = self.mail_message.browse(cr, uid, self.mail_message.search(cr, uid, [('message_id', '=', test_msg_id)])[0])
# Test: author_id set, not email_from
self.assertEqual(new_mail.author_id, user_raoul.partner_id, 'message process wrong author found')
- self.assertFalse(new_mail.email_from, 'message process should not set the email_from when an author is found')
+ self.assertEqual(new_mail.email_from, user_raoul.email, 'message process wrong email_from')
# Do: post a new message, with a unknown partner
test_msg_id = '<deadcafe.1337-3@smtp.agrolait.com>'
@@ -380,7 +380,8 @@ class test_mail(TestMailBase):
# 1. Post a new email comment on Pigs
self._init_mock_build_email()
msg2_id = self.mail_group.message_post(cr, user_raoul.id, self.group_pigs_id, body=_body2, type='email', subtype='mt_comment',
- partner_ids=[(6, 0, [p_d_id])], parent_id=msg1_id, attachments=_attachments)
+ partner_ids=[p_d_id], parent_id=msg1_id, attachments=_attachments,
+ context={'mail_post_autofollow': True})
message2 = self.mail_message.browse(cr, uid, msg2_id)
sent_emails = self._build_email_kwargs_list
self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg2_id)]), 'mail.mail notifications should have been auto-deleted!')
@@ -476,7 +477,7 @@ class test_mail(TestMailBase):
self.assertEqual(compose.res_id, self.group_pigs_id, 'mail.compose.message incorrect res_id')
# 2. Post the comment, get created message
- mail_compose.send_mail(cr, uid, [compose_id])
+ mail_compose.send_mail(cr, uid, [compose_id], {'mail_post_autofollow': True})
group_pigs.refresh()
message = group_pigs.message_ids[0]
# Test: mail.message: subject, body inside pre
diff --git a/addons/mail/tests/test_mail_message.py b/addons/mail/tests/test_mail_message.py
index 07a87a16910..f422219000c 100644
--- a/addons/mail/tests/test_mail_message.py
+++ b/addons/mail/tests/test_mail_message.py
@@ -311,8 +311,8 @@ class test_mail_access_rights(TestMailBase):
user_bert_id, user_raoul_id = self.user_bert_id, self.user_raoul_id
# Prepare groups: Pigs (employee), Jobs (public)
- pigs_msg_id = self.mail_group.message_post(cr, uid, self.group_pigs_id, body='Message', partner_ids=[(4, self.partner_admin_id)])
- jobs_msg_id = self.mail_group.message_post(cr, uid, self.group_jobs_id, body='Message', partner_ids=[(4, self.partner_admin_id)])
+ pigs_msg_id = self.mail_group.message_post(cr, uid, self.group_pigs_id, body='Message', partner_ids=[self.partner_admin_id])
+ jobs_msg_id = self.mail_group.message_post(cr, uid, self.group_jobs_id, body='Message', partner_ids=[self.partner_admin_id])
# ----------------------------------------
# CASE1: Bert, without groups
diff --git a/addons/mail/wizard/invite.py b/addons/mail/wizard/invite.py
index c3953818b4c..d3ec9a1ec14 100644
--- a/addons/mail/wizard/invite.py
+++ b/addons/mail/wizard/invite.py
@@ -77,6 +77,7 @@ class invite_wizard(osv.osv_memory):
# send an email if option checked and if a message exists (do not send void emails)
if wizard.send_mail and wizard.message:
# add signature
+ # FIXME 8.0: use notification_email_send, send a wall message and let mail handle email notification + message box
signature_company = self.pool.get('mail.notification').get_signature_footer(cr, uid, user_id=uid, res_model=wizard.res_model, res_id=wizard.res_id, context=context)
wizard.message = tools.append_content_to_html(wizard.message, signature_company, plaintext=False, container_tag='div')
diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py
index ec0c2a8de06..5776a8e46c5 100644
--- a/addons/mail/wizard/mail_compose_message.py
+++ b/addons/mail/wizard/mail_compose_message.py
@@ -191,6 +191,7 @@ class mail_compose_message(osv.TransientModel):
if context is None:
context = {}
active_ids = context.get('active_ids')
+ is_log = context.get('mail_compose_log', False)
for wizard in self.browse(cr, uid, ids, context=context):
mass_mail_mode = wizard.composition_mode == 'mass_mail'
@@ -204,26 +205,22 @@ class mail_compose_message(osv.TransientModel):
'subject': wizard.subject,
'body': wizard.body,
'parent_id': wizard.parent_id and wizard.parent_id.id,
- 'partner_ids': [(4, partner.id) for partner in wizard.partner_ids],
+ 'partner_ids': [partner.id for partner in wizard.partner_ids],
'attachments': [(attach.datas_fname or attach.name, base64.b64decode(attach.datas)) for attach in wizard.attachment_ids],
}
# mass mailing: render and override default values
if mass_mail_mode and wizard.model:
email_dict = self.render_message(cr, uid, wizard, res_id, context=context)
new_partner_ids = email_dict.pop('partner_ids', [])
- post_values['partner_ids'] += [(4, partner_id) for partner_id in new_partner_ids]
+ post_values['partner_ids'] += new_partner_ids
new_attachments = email_dict.pop('attachments', [])
post_values['attachments'] += new_attachments
post_values.update(email_dict)
- # automatically subscribe recipients if asked to
- if context.get('mail_post_autofollow') and wizard.model and post_values.get('partner_ids'):
- active_model_pool.message_subscribe(cr, uid, [res_id], [item[1] for item in post_values.get('partner_ids')], context=context)
# post the message
- active_model_pool.message_post(cr, uid, [res_id], type='comment', subtype='mt_comment', context=context, **post_values)
-
- # post process: update attachments, because id is not necessarily known when adding attachments in Chatter
- # self.pool.get('ir.attachment').write(cr, uid, [attach.id for attach in wizard.attachment_ids], {
- # 'res_id': wizard.id, 'res_model': wizard.model or False}, context=context)
+ subtype = 'mail.mt_comment'
+ if is_log:
+ subtype = False
+ active_model_pool.message_post(cr, uid, [res_id], type='comment', subtype=subtype, context=context, **post_values)
return {'type': 'ir.actions.act_window_close'}
@@ -258,7 +255,7 @@ class mail_compose_message(osv.TransientModel):
result = eval(exp, {
'user': self.pool.get('res.users').browse(cr, uid, uid, context=context),
'object': self.pool.get(model).browse(cr, uid, res_id, context=context),
- 'context': dict(context), # copy context to prevent side-effects of eval
+ 'context': dict(context), # copy context to prevent side-effects of eval
})
return result and tools.ustr(result) or ''
return template and EXPRESSION_PATTERN.sub(merge, template)
diff --git a/addons/mail/wizard/mail_compose_message_view.xml b/addons/mail/wizard/mail_compose_message_view.xml
index fca5ec65ca0..00ba33a6d6f 100644
--- a/addons/mail/wizard/mail_compose_message_view.xml
+++ b/addons/mail/wizard/mail_compose_message_view.xml
@@ -13,8 +13,9 @@
<field name="res_id" invisible="1"/>
<field name="parent_id" invisible="1"/>
<!-- visible wizard -->
- <label for="partner_ids" string="Recipients"/>
- <div groups="base.group_user">
+ <label for="partner_ids" string="Recipients"
+ invisible="context.get('mail_compose_log', False)"/>
+ <div groups="base.group_user" invisible="context.get('mail_compose_log', False)">
<span attrs="{'invisible':['|', ('model', '=', False), ('composition_mode', '!=', 'mass_mail')]}">
Followers of selected items and
</span>
diff --git a/addons/marketing_campaign/marketing_campaign_view.xml b/addons/marketing_campaign/marketing_campaign_view.xml
index 2b479be709c..2d7501a8333 100644
--- a/addons/marketing_campaign/marketing_campaign_view.xml
+++ b/addons/marketing_campaign/marketing_campaign_view.xml
@@ -59,7 +59,7 @@
<button name="state_running_set" string="Run" states="draft" class="oe_highlight"/>
<button name="state_done_set" string="Done" states="running" class="oe_highlight"/>
<button name="state_draft_set" string="Set to Draft" states="done,cancelled"/>
- <button name="state_cancel_set" string="Cancel" states="running"/>
+ <button name="state_cancel_set" string="Cancel Campaign" states="running"/>
<field name="state" readonly="1" widget="statusbar" statusbar_visible="draft,running,done"/>
</header>
<sheet string="Campaign">
@@ -190,7 +190,7 @@
<button name="state_running_set" string="Run" states="draft" class="oe_highlight"/>
<button name="state_done_set" string="Close" states="running" class="oe_highlight"/>
<button name="state_draft_set" string="Set to Draft" states="done,cancelled"/>
- <button name="state_cancel_set" string="Cancel" states="running"/>
+ <button name="state_cancel_set" string="Cancel Segment" states="running"/>
<field name="state" readonly="1" widget="statusbar" statusbar_visible="draft,running,done"/>
</header>
<sheet string="Segments" >
@@ -387,7 +387,7 @@
<header>
<button string="Process" states="todo" name="process" type="object"/>
<button string="Reset" states="exception,cancelled" name="button_draft" type="object"/>
- <button string="Cancel" states="todo,exception" name="button_cancel" type="object"/>
+ <button string="Cancel Workitem" states="todo,exception" name="button_cancel" type="object"/>
<field name="state" readonly="True" widget="statusbar" statusbar_visible="todo,done" statusbar_colors='{"exception":"red"}'/>
<button string="Preview" states="todo" name="preview" icon="gtk-zoom-fit" type="object"/>
</header>
diff --git a/addons/mrp/mrp_view.xml b/addons/mrp/mrp_view.xml
index bde5abee2b5..f3da234463c 100644
--- a/addons/mrp/mrp_view.xml
+++ b/addons/mrp/mrp_view.xml
@@ -625,8 +625,8 @@
<button name="force_production" states="picking_except" string="Force Reservation" type="object"/>
<button name="button_produce" states="ready" string="Mark as Started"/>
<button name="button_recreate" states="picking_except" string="Recreate Picking"/>
- <button name="button_cancel" states="draft,ready,in_production,picking_except" string="Cancel"/>
- <button name="action_cancel" type="object" states="confirmed" string="Cancel"/>
+ <button name="button_cancel" states="draft,ready,in_production,picking_except" string="Cancel Production"/>
+ <button name="action_cancel" type="object" states="confirmed" string="Cancel Production"/>
<field name="state" widget="statusbar" statusbar_visible="draft,ready,in_production,done" statusbar_colors='{"picking_except":"red","confirmed":"blue"}'/>
</header>
<sheet>
diff --git a/addons/mrp_operations/mrp_operations_view.xml b/addons/mrp_operations/mrp_operations_view.xml
index dbe5ed83d0d..207c5d975d7 100644
--- a/addons/mrp_operations/mrp_operations_view.xml
+++ b/addons/mrp_operations/mrp_operations_view.xml
@@ -10,7 +10,7 @@
<xpath expr="//field[@name='workcenter_lines']/form//field[@name='name']" position="before">
<header colspan="8">
<button name="button_start_working" string="Start" states="draft" icon="terp-gtk-jump-to-ltr" help="Start Working"/>
- <button name="button_cancel" string="Cancel" states="draft,startworking" icon="gtk-stop" help="Cancel Order"/>
+ <button name="button_cancel" string="Cancel Order" states="draft,startworking" icon="gtk-stop" help="Cancel Order"/>
<button name="button_draft" string="Set Draft" states="cancel" icon="gtk-convert" help="Set to Draft"/>
<button name="button_resume" string="Resume" states="pause" icon="gtk-media-pause" help="Resume Work Order"/>
<button name="button_pause" string="Pending" states="startworking" icon="gtk-media-pause" help="Pause Work Order"/>
diff --git a/addons/point_of_sale/account_bank_statement.py b/addons/point_of_sale/account_bank_statement.py
index 4f88f8e3650..31415bb847c 100644
--- a/addons/point_of_sale/account_bank_statement.py
+++ b/addons/point_of_sale/account_bank_statement.py
@@ -27,7 +27,7 @@ class account_journal(osv.osv):
_columns = {
'journal_user': fields.boolean('PoS Payment Method', help="Check this box if this journal define a payment method that can be used in point of sales."),
- 'amount_authorized_diff' : fields.float('Amount Authorized Difference'),
+ 'amount_authorized_diff' : fields.float('Amount Authorized Difference', help="This field depicts the maximum difference allowed between the ending balance and the theorical cash when closing a session, for non-POS managers. If this maximum is reached, the user will have an error message at the closing of his session saying that he needs to contact his manager."),
'self_checkout_payment_method' : fields.boolean('Self Checkout Payment Method'),
}
_defaults = {
diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py
index ea7460dae21..af5d7aa663c 100644
--- a/addons/point_of_sale/point_of_sale.py
+++ b/addons/point_of_sale/point_of_sale.py
@@ -704,6 +704,7 @@ class pos_order(osv.osv):
@return: True
"""
stock_picking_obj = self.pool.get('stock.picking')
+ wf_service = netsvc.LocalService("workflow")
for order in self.browse(cr, uid, ids, context=context):
stock_picking_obj.signal_button_cancel(cr, uid, [order.picking_id.id])
if stock_picking_obj.browse(cr, uid, order.picking_id.id, context=context).state <> 'cancel':
@@ -974,8 +975,12 @@ class pos_order(osv.osv):
else:
grouped_data[key].append(values)
+ #because of the weird way the pos order is written, we need to make sure there is at least one line,
+ #because just after the 'for' loop there are references to 'line' and 'income_account' variables (that
+ #are set inside the for loop)
+ #TOFIX: a deep refactoring of this method (and class!) is needed in order to get rid of this stupid hack
+ assert order.lines, _('The POS order must have lines when calling this method')
# Create an move for each order line
-
for line in order.lines:
tax_amount = 0
taxes = [t for t in line.product_id.taxes_id]
@@ -1050,7 +1055,7 @@ class pos_order(osv.osv):
'name': _('Tax') + ' ' + tax.name,
'quantity': line.qty,
'product_id': line.product_id.id,
- 'account_id': key[account_pos],
+ 'account_id': key[account_pos] or income_account,
'credit': ((tax_amount>0) and tax_amount) or 0.0,
'debit': ((tax_amount<0) and -tax_amount) or 0.0,
'tax_code_id': key[tax_code_pos],
@@ -1118,9 +1123,9 @@ class pos_order_line(osv.osv):
account_tax_obj = self.pool.get('account.tax')
cur_obj = self.pool.get('res.currency')
for line in self.browse(cr, uid, ids, context=context):
- taxes = line.product_id.taxes_id
+ taxes_ids = [ tax for tax in line.product_id.taxes_id if tax.company_id.id == line.order_id.company_id.id ]
price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
- taxes = account_tax_obj.compute_all(cr, uid, line.product_id.taxes_id, price, line.qty, product=line.product_id, partner=line.order_id.partner_id or False)
+ taxes = account_tax_obj.compute_all(cr, uid, taxes_ids, price, line.qty, product=line.product_id, partner=line.order_id.partner_id or False)
cur = line.order_id.pricelist_id.currency_id
res[line.id]['price_subtotal'] = cur_obj.round(cr, uid, cur, taxes['total'])
diff --git a/addons/point_of_sale/static/src/js/db.js b/addons/point_of_sale/static/src/js/db.js
index 2be58d6ce97..a79b111b5c5 100644
--- a/addons/point_of_sale/static/src/js/db.js
+++ b/addons/point_of_sale/static/src/js/db.js
@@ -178,7 +178,7 @@ function openerp_pos_db(instance, module){
var ancestors = this.get_category_ancestors_ids(categ_id) || [];
- for(var j = 0; j < ancestors.length; j++){
+ for(var j = 0, jlen = ancestors.length; j < jlen; j++){
var ancestor = ancestors[j];
if(! stored_categories[ancestor]){
stored_categories[ancestor] = [];
diff --git a/addons/point_of_sale/test/01_order_to_payment.yml b/addons/point_of_sale/test/01_order_to_payment.yml
index 9534e79095f..bf201affc4a 100644
--- a/addons/point_of_sale/test/01_order_to_payment.yml
+++ b/addons/point_of_sale/test/01_order_to_payment.yml
@@ -27,10 +27,23 @@
account_collected_id: account.iva
price_include: 0
-
- I assign this 5 percent tax on the PCSC349 product as a sale tax
+ I will create a second VAT tax of 5% but this time for a child company, to
+ ensure that only product taxes of the current session's company are considered
+ (this tax should be ignore when computing order's taxes in following tests)
+-
+ !record {model: account.tax, id: account_tax_05_incl_chicago}:
+ name: VAT 05 perc Excl (US)
+ type: percent
+ amount: 0.05
+ account_paid_id: account.iva
+ account_collected_id: account.iva
+ price_include: 0
+ company_id: stock.res_company_1
+-
+ I assign those 5 percent taxes on the PCSC349 product as a sale taxes
-
!record {model: product.product, id: product.product_product_4}:
- taxes_id: [account_tax_05_incl]
+ taxes_id: [account_tax_05_incl, account_tax_05_incl_chicago]
-
I create a new session
-
diff --git a/addons/portal/portal_view.xml b/addons/portal/portal_view.xml
index 203dcde90c7..d56e277c3fb 100644
--- a/addons/portal/portal_view.xml
+++ b/addons/portal/portal_view.xml
@@ -9,12 +9,13 @@
sequence="15"/>
<menuitem name="Messaging" id="portal_messages" parent="portal_menu" sequence="10" groups="portal.group_portal"/>
+
<record id="action_mail_inbox_feeds_portal" model="ir.actions.client">
<field name="name">Inbox</field>
<field name="tag">mail.wall</field>
<field name="res_model">mail.message</field>
<field name="context">{
- 'default_model': 'res.users',
+ 'default_model': 'res.users',
'default_res_id': uid,
}</field>
<field name="params" eval="&quot;{
@@ -38,8 +39,70 @@
</p>
</field>
</record>
+
+ <record id="action_mail_star_feeds_portal" model="ir.actions.client">
+ <field name="name">To-do</field>
+ <field name="tag">mail.wall</field>
+ <field name="res_model">mail.message</field>
+ <field name="context">{
+ 'default_model': 'res.users',
+ 'default_res_id': uid,
+ 'search_default_message_unread': True
+ }</field>
+ <field name="params" eval="&quot;{
+ 'domain': [
+ ('starred', '=', True),
+ ],
+ 'show_compose_message': False,
+ 'show_link': False,
+ 'view_mailbox': True,
+ 'read_action': 'read'
+ }&quot;"/>
+ <field name="help" type="html">
+ <p>
+ <b>No todo.</b>
+ </p><p>
+ When you process messages in your inbox, you can mark some
+ as <i>todo</i>. From this menu, you can process all your todo.
+ </p>
+ </field>
+ </record>
+
+ <record id="action_mail_archives_feeds_portal" model="ir.actions.client">
+ <field name="name">Archives</field>
+ <field name="tag">mail.wall</field>
+ <field name="context">{
+ 'default_model': 'res.users',
+ 'default_res_id': uid
+ }</field>
+ <field name="params" eval="&quot;{
+ 'domain': [
+ '|',
+ ('notification_ids.partner_id.user_ids', 'in', [uid]),
+ ('author_id.user_ids', 'in', [uid]),
+ ],
+ 'show_compose_message': False,
+ 'show_link': False,
+ 'view_mailbox': True,
+ 'read_action': 'read'
+ }&quot;"/>
+ <field name="help" type="html">
+ <p>
+ No message found and no message sent yet.
+ </p><p>
+ Click on the top-right icon to compose a message. This
+ message will be sent by email if it's an internal contact.
+ </p>
+ </field>
+ </record>
+
<menuitem name="Inbox" id="portal_inbox" parent="portal_messages"
action="action_mail_inbox_feeds_portal" sequence="10" groups="portal.group_portal"/>
+ <menuitem name="To-do" id="portal_mail_starfeeds" parent="portal_messages"
+ action="action_mail_star_feeds_portal" sequence="20" groups="portal.group_portal"/>
+ <menuitem name="Archives" id="portal_mail_archivesfeeds" parent="portal_messages"
+ action="action_mail_archives_feeds_portal" sequence="30" groups="portal.group_portal"/>
+
<!--
Create menu items that we'll leave empty for now - they'll be
filled up by other portal modules.
diff --git a/addons/portal_anonymous/static/src/js/portal_anonymous.js b/addons/portal_anonymous/static/src/js/portal_anonymous.js
index 92060aa6f99..a719a285908 100644
--- a/addons/portal_anonymous/static/src/js/portal_anonymous.js
+++ b/addons/portal_anonymous/static/src/js/portal_anonymous.js
@@ -44,12 +44,10 @@ openerp.portal_anonymous = function(instance) {
start: function() {
var self = this;
return $.when(this._super()).then(function() {
- var params = $.deparam($.param.querystring());
- var dblist = self.db_list || [];
- if (!self.session.session_is_valid() && dblist.length === 1 && (!params.token || !params.login)) {
+ if (!self.session.session_is_valid() && !(self.params.token || self.params.login)) {
self.remember_credentials = false;
// XXX get login/pass from server (via a rpc call) ?
- return self.do_login(dblist[0], 'anonymous', 'anonymous');
+ return self.do_login(self.selected_db, 'anonymous', 'anonymous');
}
});
},
@@ -69,16 +67,18 @@ openerp.portal_anonymous = function(instance) {
this.$el.find('a.login').click(function() {
var p = self.getParent();
var am = p.action_manager;
- p.$el.find('.oe_leftbar').hide();
- am.do_action({
- type:'ir.actions.client',
- tag:'login',
- target: 'current',
- params: {
- login_successful: function() {
- am.do_action("reload");
+ p.$el.find('.oe_leftbar, .oe_topbar').hide();
+ self.session.session_logout().done(function () {
+ am.do_action({
+ type:'ir.actions.client',
+ tag:'login',
+ target: 'current',
+ params: {
+ login_successful: function() {
+ am.do_action("reload");
+ }
}
- }
+ });
});
});
}
diff --git a/addons/portal_sale/portal_sale_view.xml b/addons/portal_sale/portal_sale_view.xml
index 3e2fc18d877..ac56cc9ddfc 100644
--- a/addons/portal_sale/portal_sale_view.xml
+++ b/addons/portal_sale/portal_sale_view.xml
@@ -24,6 +24,16 @@
</field>
</record>
+ <record id="view_account_invoice_filter_share" model="ir.ui.view">
+ <field name="name">account.invoice.select.share</field>
+ <field name="model">account.invoice</field>
+ <field name="inherit_id" ref="account.view_account_invoice_filter"/>
+ <field name="arch" type="xml">
+ <xpath expr='//field[@name="user_id"]' position="attributes">
+ <attribute name="domain">[('share','=', False)]</attribute>
+ </xpath>
+ </field>
+ </record>
<!--
Override the original action to set another help field and/or
diff --git a/addons/procurement/procurement.py b/addons/procurement/procurement.py
index 299d1494fa6..672859db909 100644
--- a/addons/procurement/procurement.py
+++ b/addons/procurement/procurement.py
@@ -67,10 +67,11 @@ class StockMove(osv.osv):
'procurements': fields.one2many('procurement.order', 'move_id', 'Procurements'),
}
- def copy(self, cr, uid, id, default=None, context=None):
- default = default or {}
+ def copy_data(self, cr, uid, id, default=None, context=None):
+ if default is None:
+ default = {}
default['procurements'] = []
- return super(StockMove, self).copy(cr, uid, id, default, context=context)
+ return super(StockMove, self).copy_data(cr, uid, id, default, context=context)
class procurement_order(osv.osv):
diff --git a/addons/procurement/procurement_view.xml b/addons/procurement/procurement_view.xml
index c4bff95a2fa..7f0a28d94dc 100644
--- a/addons/procurement/procurement_view.xml
+++ b/addons/procurement/procurement_view.xml
@@ -47,7 +47,7 @@
<button name="button_confirm" states="draft" string="Confirm" class="oe_highlight"/>
<button name="button_check" states="confirmed" string="Run Procurement" class="oe_highlight"/>
<button name="button_restart" states="exception" string="Retry" class="oe_highlight"/>
- <button name="button_cancel" states="draft,exception,waiting" string="Cancel"/>
+ <button name="button_cancel" states="draft,exception,waiting" string="Cancel Procurement"/>
<field name="state" readonly="1" widget="statusbar" statusbar_visible="draft,confirmed" />
</header>
<sheet>
diff --git a/addons/product/product.py b/addons/product/product.py
index 1cacb58df23..33225c92c6f 100644
--- a/addons/product/product.py
+++ b/addons/product/product.py
@@ -554,7 +554,7 @@ class product_product(osv.osv):
'price_extra': fields.float('Variant Price Extra', digits_compute=dp.get_precision('Product Price')),
'price_margin': fields.float('Variant Price Margin', digits_compute=dp.get_precision('Product Price')),
'pricelist_id': fields.dummy(string='Pricelist', relation='product.pricelist', type='many2one'),
- 'name_template': fields.related('product_tmpl_id', 'name', string="Name", type='char', size=128, store=True, select=True),
+ 'name_template': fields.related('product_tmpl_id', 'name', string="Template Name", type='char', size=128, store=True, select=True),
'color': fields.integer('Color Index'),
# image: all image fields are base64 encoded and PIL-supported
'image': fields.binary("Image",
diff --git a/addons/product/product_view.xml b/addons/product/product_view.xml
index 1584cbbc1f0..ddb1ab3809f 100644
--- a/addons/product/product_view.xml
+++ b/addons/product/product_view.xml
@@ -581,7 +581,7 @@
<field name="delay"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
</group>
- <field groups="product.group_sale_pricelist" name="pricelist_ids">
+ <field groups="product.group_purchase_pricelist" name="pricelist_ids">
<tree editable="bottom" string="Pricelist">
<field name="min_quantity"/>
<field name="price"/>
diff --git a/addons/product/security/product_security.xml b/addons/product/security/product_security.xml
index 2e6bdac9fca..3cb54f84d47 100644
--- a/addons/product/security/product_security.xml
+++ b/addons/product/security/product_security.xml
@@ -1,9 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="0">
+ <!-- Change name of group for 7.0 users since product variant module doesn't exist anymore
+ DO NOT CHANGE IT IN TRUNK -->
<record id="group_product_variant" model="res.groups">
- <field name="name">Product Variant</field>
+ <field name="name">Product Variant (not supported)</field>
<field name="category_id" ref="base.module_category_hidden"/>
</record>
diff --git a/addons/project/project.py b/addons/project/project.py
index 831bea64ce8..0751865d9c4 100644
--- a/addons/project/project.py
+++ b/addons/project/project.py
@@ -1302,13 +1302,13 @@ class account_analytic_account(osv.osv):
return analytic_account_id
def write(self, cr, uid, ids, vals, context=None):
- name = vals.get('name')
+ vals_for_project = vals.copy()
for account in self.browse(cr, uid, ids, context=context):
- if not name:
- vals['name'] = account.name
+ if not vals.get('name'):
+ vals_for_project['name'] = account.name
if not vals.get('type'):
- vals['type'] = account.type
- self.project_create(cr, uid, account.id, vals, context=context)
+ vals_for_project['type'] = account.type
+ self.project_create(cr, uid, account.id, vals_for_project, context=context)
return super(account_analytic_account, self).write(cr, uid, ids, vals, context=context)
def unlink(self, cr, uid, ids, *args, **kwargs):
@@ -1425,3 +1425,4 @@ class project_category(osv.osv):
_columns = {
'name': fields.char('Name', size=64, required=True, translate=True),
}
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/project/project_view.xml b/addons/project/project_view.xml
index 1f30521f07f..16dbe44ccc1 100644
--- a/addons/project/project_view.xml
+++ b/addons/project/project_view.xml
@@ -77,7 +77,7 @@
<button name="set_template" string="Set as Template" type="object" states="open" groups="base.group_user"/>
<button string="New Project Based on Template" name="duplicate_template" type="object" states="template" context="{'parent_id':parent_id}" class="oe_highlight" groups="base.group_user"/>
<button name="reset_project" string="Reset as Project" type="object" states="template" class="oe_highlight" groups="base.group_user"/>
- <button name="set_cancel" string="Cancel" type="object" states="open,pending" groups="base.group_user"/>
+ <button name="set_cancel" string="Cancel Project" type="object" states="open,pending" groups="base.group_user"/>
<field name="state" widget="statusbar" statusbar_visible="open,close" statusbar_colors='{"pending":"blue"}' readonly="1"/>
</header>
<sheet string="Project">
@@ -155,7 +155,7 @@
</group>
</group>
</page>
- <page string="Tasks Stages" attrs="{'invisible': [('use_tasks', '=', False)]}">
+ <page string="Project Stages" attrs="{'invisible': [('use_tasks', '=', False)]}" name="project_stages">
<field name="type_ids"/>
</page>
</notebook>
@@ -485,7 +485,6 @@
<field name="remaining_hours" sum="Remaining Time" groups="project.group_time_work_estimation_tasks"/>
<field name="date_deadline"/>
<field name="message_summary"/>
- <field name="message_unread"/>
<field name="categ_ids"/>
<templates>
<t t-name="kanban-box">
@@ -532,6 +531,7 @@
</span>
</div>
<div class="oe_kanban_footer_left" style="margin-top:5px;">
+ <t t-raw="record.message_summary.raw_value"/>
<field name="categ_ids"/>
</div>
</div>
@@ -757,5 +757,36 @@
<!-- User Form -->
<act_window context="{'search_default_user_id': [active_id], 'default_user_id': active_id}" domain="[('state', '&lt;&gt;', 'cancelled'),('state', '&lt;&gt;', 'done')]" id="act_res_users_2_project_task_opened" name="Assigned Tasks" res_model="project.task" src_model="res.users" view_mode="tree,form,gantt,calendar,graph" view_type="form"/>
+ <!-- Tags -->
+ <record model="ir.ui.view" id="project_category_search_view">
+ <field name="name">Tags</field>
+ <field name="model">project.category</field>
+ <field name="arch" type="xml">
+ <search string="Issue Version">
+ <field name="name"/>
+ </search>
+ </field>
+ </record>
+ <record model="ir.ui.view" id="project_category_form_view">
+ <field name="name">Tags</field>
+ <field name="model">project.category</field>
+ <field name="arch" type="xml">
+ <form string="Tags">
+ <field name="name"/>
+ </form>
+ </field>
+ </record>
+ <record id="project_category_action" model="ir.actions.act_window">
+ <field name="name">Tags</field>
+ <field name="res_model">project.category</field>
+ <field name="view_type">form</field>
+ <field name="help" type="html">
+ <p class="oe_view_nocontent_create">
+ Click to add a new tag.
+ </p>
+ </field>
+ </record>
+ <menuitem action="project_category_action" id="menu_project_category_act" parent="base.menu_definitions" groups="base.group_no_one"/>
+
</data>
</openerp>
diff --git a/addons/project_issue/project_issue.py b/addons/project_issue/project_issue.py
index 8eb7281cd24..9233a95634c 100644
--- a/addons/project_issue/project_issue.py
+++ b/addons/project_issue/project_issue.py
@@ -20,6 +20,7 @@
##############################################################################
from openerp.addons.base_status.base_stage import base_stage
+from openerp.addons.project.project import _TASK_STATE
from openerp.addons.crm import crm
from datetime import datetime
from openerp.osv import fields,osv
@@ -41,9 +42,6 @@ class project_issue_version(osv.osv):
}
project_issue_version()
-_ISSUE_STATE = [('draft', 'New'), ('open', 'In Progress'), ('cancel', 'Cancelled'), ('done', 'Done'), ('pending', 'Pending')]
-
-
class project_issue(base_stage, osv.osv):
_name = "project.issue"
_description = "Project Issue"
@@ -252,7 +250,7 @@ class project_issue(base_stage, osv.osv):
'company_id': fields.many2one('res.company', 'Company'),
'description': fields.text('Private Note'),
'state': fields.related('stage_id', 'state', type="selection", store=True,
- selection=_ISSUE_STATE, string="Status", readonly=True,
+ selection=_TASK_STATE, string="Status", readonly=True,
help='The status is set to \'Draft\', when a case is created.\
If the case is in progress the status is set to \'Open\'.\
When the case is over, the status is set to \'Done\'.\
@@ -485,6 +483,15 @@ class project_issue(base_stage, osv.osv):
return [issue.project_id.message_get_reply_to()[0] if issue.project_id else False
for issue in self.browse(cr, uid, ids, context=context)]
+ def message_get_suggested_recipients(self, cr, uid, ids, context=None):
+ recipients = super(project_issue, self).message_get_suggested_recipients(cr, uid, ids, context=context)
+ for issue in self.browse(cr, uid, ids, context=context):
+ if issue.partner_id:
+ self._message_add_suggested_recipient(cr, uid, recipients, issue, partner=issue.partner_id, reason=_('Customer'))
+ elif issue.email_from:
+ self._message_add_suggested_recipient(cr, uid, recipients, issue, email=issue.email_from, reason=_('Customer Email'))
+ return recipients
+
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.
@@ -549,12 +556,13 @@ class project(osv.osv):
res = dict.fromkeys(ids, 0)
issue_ids = self.pool.get('project.issue').search(cr, uid, [('project_id', 'in', ids)])
for issue in self.pool.get('project.issue').browse(cr, uid, issue_ids, context):
- res[issue.project_id.id] += 1
+ if issue.state not in ('done', 'cancelled'):
+ res[issue.project_id.id] += 1
return res
_columns = {
'project_escalation_id' : fields.many2one('project.project','Project Escalation', help='If any issue is escalated from the current Project, it will be listed under the project selected here.', states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}),
- 'issue_count': fields.function(_issue_count, type='integer'),
+ 'issue_count': fields.function(_issue_count, type='integer', string="Unclosed Issues"),
}
def _check_escalation(self, cr, uid, ids, context=None):
diff --git a/addons/project_issue/project_issue_data.xml b/addons/project_issue/project_issue_data.xml
index 591e6c04878..2e43ca69193 100644
--- a/addons/project_issue/project_issue_data.xml
+++ b/addons/project_issue/project_issue_data.xml
@@ -2,21 +2,6 @@
<openerp>
<data noupdate="1">
- <!-- Case Categories -->
- <!-- For Bugs -->
-
- <record model="crm.case.categ" id="bug_categ">
- <field name="name">Maintenance</field>
- <field name="section_id" ref="crm.section_sales_department"/>
- <field name="object_id" search="[('model','=','project.issue')]" model="ir.model"/>
- </record>
-
- <record model="crm.case.categ" id="feature_request_categ">
- <field name="name">Feature Requests</field>
- <field name="section_id" ref="crm.section_sales_department"/>
- <field name="object_id" search="[('model','=','project.issue')]" model="ir.model"/>
- </record>
-
<!-- Case type_id -->
<!-- For Bugs -->
<record model="project.issue.version" id="type1">
diff --git a/addons/project_issue/project_issue_view.xml b/addons/project_issue/project_issue_view.xml
index 6ffa75dd926..500a1376422 100644
--- a/addons/project_issue/project_issue_view.xml
+++ b/addons/project_issue/project_issue_view.xml
@@ -41,16 +41,6 @@
</record>
<menuitem action="project_issue_version_action" id="menu_project_issue_version_act" parent="menu_project_confi" groups="base.group_no_one"/>
- <record id="project_issue_categ_action" model="ir.actions.act_window">
- <field name="name">Issue Categories</field>
- <field name="res_model">crm.case.categ</field>
- <field name="view_type">form</field>
- <field name="view_id" ref="crm.crm_case_categ_tree-view"/>
- <field name="domain">[('object_id.model', '=', 'project.issue')]</field>
- <field name="context" eval="{'object_id': ref('model_project_issue')}"/>
- </record>
-
- <menuitem action="project_issue_categ_action" name="Categories" id="menu_project_issue_category_act" parent="menu_project_confi" groups="base.group_no_one"/>
<record model="ir.ui.view" id="project_issue_form_view">
<field name="name">Project Issue Tracker Form</field>
@@ -62,7 +52,7 @@
states="open" groups="base.group_user"/>
<button name="case_close" string="Done" type="object"
states="draft,pending" groups="base.group_user"/>
- <button name="case_cancel" string="Cancel" type="object"
+ <button name="case_cancel" string="Cancel Issue" type="object"
states="draft,open,pending" groups="base.group_user"/>
<field name="stage_id" widget="statusbar" clickable="True"/>
</header>
@@ -75,6 +65,8 @@
<button name="set_kanban_state_blocked" help="Ready for Next Stage" attrs="{'invisible': [('kanban_state', 'in', ['normal','blocked'])]}" type="object" icon="gtk-yes" class="oe_link oe_right"/>
<button name="set_kanban_state_normal" help="Blocked" attrs="{'invisible': [('kanban_state', 'in', ['done','normal'])]}" type="object" icon="gtk-no" class="oe_link oe_right"/>
</h1>
+ <label for="categ_ids" class="oe_edit_only"/>
+ <field name="categ_ids" widget="many2many_tags"/>
<group>
<group groups="base.group_user">
<field name="user_id"
@@ -88,7 +80,7 @@
</div>
</group>
<group>
- <field name="categ_ids" widget="many2many_tags"/>
+ <field name="id"/>
<field name="version_id" groups="base.group_user"/>
<field name="priority" groups="base.group_user"/>
<label for="task_id" groups="base.group_user"/>
@@ -111,11 +103,8 @@
<field name="inactivity_days"/>
<field name="days_since_creation"/>
</group>
- <group string="References">
- <field name="id"/>
- <field name="active"/>
- </group>
<group string="Status" groups="base.group_no_one">
+ <field name="active"/>
<field name="state" string="Status"/>
</group>
</page>
@@ -209,6 +198,7 @@
<field name="user_id"/>
<field name="date_deadline"/>
<field name="kanban_state"/>
+ <field name="message_summary"/>
<templates>
<t t-name="kanban-tooltip">
<ul class="oe_kanban_tooltip">
@@ -259,6 +249,9 @@
<img t-att-src="kanban_image('res.users', 'image_small', record.user_id.raw_value)" t-att-title="record.user_id.value" width="24" height="24" class="oe_kanban_avatar"/>
</div>
</div>
+ <div class="oe_kanban_footer_left">
+ <t t-raw="record.message_summary.raw_value"/>
+ </div>
</div>
<div class="oe_clear"></div>
</div>
@@ -325,6 +318,9 @@
<xpath expr='//div[@name="buttons"]' position='inside'>
<button name="%(act_project_project_2_project_issue_all)d" string="Issues" type="action" attrs="{'invisible':[('use_issues','=', 0)]}"/>
</xpath>
+ <xpath expr='//page[@name="project_stages"]' position="attributes">
+ <attribute name="attrs">{'invisible': [('use_tasks', '=', False),('use_issues','=',False)]}</attribute>
+ </xpath>
<field name="priority" position="before">
<field name="project_escalation_id"/>
</field>
diff --git a/addons/project_issue/security/ir.model.access.csv b/addons/project_issue/security/ir.model.access.csv
index 5799fb0b611..ef4edfdc13a 100644
--- a/addons/project_issue/security/ir.model.access.csv
+++ b/addons/project_issue/security/ir.model.access.csv
@@ -1,7 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_project_issue_user,project.issue,model_project_issue,project.group_project_user,1,1,1,1
access_project_issue_report_all,project.issue.report,model_project_issue_report,project.group_project_manager,1,1,1,1
-access_crm_case_categ_id,crm.case.categ,crm.model_crm_case_categ,project.group_project_manager,1,1,1,1
access_project_issue_version_project,project_issue_version manager,model_project_issue_version,project.group_project_manager,1,1,1,1
access_project_issue_version_project_user,project_issue_version user,model_project_issue_version,project.group_project_user,1,0,0,0
access_resource_calendar_project_manager,resource.calendar.project.manager,resource.model_resource_calendar,project.group_project_manager,1,1,1,1
diff --git a/addons/project_long_term/project_long_term_view.xml b/addons/project_long_term/project_long_term_view.xml
index e8e21caab0a..4066b6133c1 100644
--- a/addons/project_long_term/project_long_term_view.xml
+++ b/addons/project_long_term/project_long_term_view.xml
@@ -146,7 +146,7 @@
<button string="Done" name="set_done" states="pending,open"/>
<button string="Pending" name="set_pending" states="open"/>
<button string="Draft" name="set_draft" states="open"/>
- <button string="Cancel" name="set_cancel" states="draft,open,pending"/>
+ <button string="Cancel Phase" name="set_cancel" states="draft,open,pending"/>
<field name="state" widget="statusbar" statusbar_visible="draft,open,done" statusbar_colors='{"pending":"blue"}'/>
</header>
<sheet>
diff --git a/addons/purchase/edi/purchase_order_action_data.xml b/addons/purchase/edi/purchase_order_action_data.xml
index d616ff1d116..3b02be574ef 100644
--- a/addons/purchase/edi/purchase_order_action_data.xml
+++ b/addons/purchase/edi/purchase_order_action_data.xml
@@ -30,7 +30,7 @@
<field name="body_html"><![CDATA[
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: #FFF; ">
- <p>Hello$ ${object.partner_id.name},</p>
+ <p>Hello ${object.partner_id.name},</p>
<p>Here is a ${object.state in ('draft', 'sent') and 'request for quotation' or 'purchase order confirmation'} from ${object.company_id.name}: </p>
diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py
index 40bfa9cc654..23f889b136f 100644
--- a/addons/purchase/purchase.py
+++ b/addons/purchase/purchase.py
@@ -1208,14 +1208,16 @@ class account_invoice(osv.Model):
res = super(account_invoice, self).invoice_validate(cr, uid, ids, context=context)
purchase_order_obj = self.pool.get('purchase.order')
po_ids = purchase_order_obj.search(cr, uid, [('invoice_ids', 'in', ids)], context=context)
- purchase_order_obj.message_post(cr, uid, po_ids, body=_("Invoice received"), context=context)
+ if po_ids:
+ purchase_order_obj.message_post(cr, uid, po_ids, body=_("Invoice received"), context=context)
return res
def confirm_paid(self, cr, uid, ids, context=None):
res = super(account_invoice, self).confirm_paid(cr, uid, ids, context=context)
purchase_order_obj = self.pool.get('purchase.order')
po_ids = purchase_order_obj.search(cr, uid, [('invoice_ids', 'in', ids)], context=context)
- purchase_order_obj.message_post(cr, uid, po_ids, body=_("Invoice paid"), context=context)
+ if po_ids:
+ purchase_order_obj.message_post(cr, uid, po_ids, body=_("Invoice paid"), context=context)
return res
diff --git a/addons/purchase/purchase_view.xml b/addons/purchase/purchase_view.xml
index 13f755f15a0..056a06f0750 100644
--- a/addons/purchase/purchase_view.xml
+++ b/addons/purchase/purchase_view.xml
@@ -161,21 +161,21 @@
<field name="arch" type="xml">
<form string="Purchase Order" version="7.0">
<header>
- <button name="wkf_send_rfq" states="draft" string="Send by EMail" type="object" context="{'send_rfq':True}" class="oe_highlight"/>
- <button name="wkf_send_rfq" states="sent" string="Send by EMail" type="object" context="{'send_rfq':True}"/>
+ <button name="wkf_send_rfq" states="draft" string="Send by Email" type="object" context="{'send_rfq':True}" class="oe_highlight"/>
+ <button name="wkf_send_rfq" states="sent" string="Send by Email" type="object" context="{'send_rfq':True}"/>
<button name="print_quotation" string="Print" type="object" states="draft" class="oe_highlight" groups="base.group_user"/>
<button name="print_quotation" string="Print" type="object" states="sent" groups="base.group_user"/>
<button name="purchase_confirm" states="draft" string="Confirm Order"/>
<button name="purchase_confirm" states="sent" string="Confirm Order" class="oe_highlight"/>
<button name="wkf_send_rfq" states="confirmed" string="Resend Purchase Order" type="object" class="oe_highlight"/>
- <button name="action_cancel" states="approved,except_picking,except_invoice" string="Cancel" type="object" />
+ <button name="action_cancel" states="approved,except_picking,except_invoice" string="Cancel Order" type="object" />
<button name="picking_ok" states="except_picking" string="Manually Corrected"/>
<button name="invoice_ok" states="except_invoice" string="Manually Corrected"/>
<button name="purchase_approve" states="confirmed" string="Approve Order" class="oe_highlight" groups="purchase.group_purchase_manager"/>
<button name="view_picking" string="Receive Products" type="object" attrs="{'invisible': ['|', ('shipped','=',True), ('state','!=', 'approved')]}" class="oe_highlight"/>
<button name="view_invoice" string="Receive Invoice" type="object" attrs="{'invisible': ['|', ('invoice_method','=','picking'), '|', ('state','!=', 'approved'), ('invoiced','=',True) ]}" class="oe_highlight"/>
<button name="action_cancel_draft" states="cancel,sent,confirmed" string="Set to Draft" type="object" />
- <button name="purchase_cancel" states="draft,confirmed,sent" string="Cancel"/>
+ <button name="purchase_cancel" states="draft,confirmed,sent" string="Cancel Order"/>
<field name="state" widget="statusbar" statusbar_visible="draft,sent,approved,done" statusbar_colors='{"except_picking":"red","except_invoice":"red","confirmed":"blue"}' readonly="1"/>
</header>
<sheet>
@@ -209,7 +209,7 @@
<field name="name"/>
<field name="date_planned"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
- <field name="account_analytic_id" groups="purchase.group_analytic_accounting" domain="[('type','in',('normal','contract'))]"/>
+ <field name="account_analytic_id" groups="purchase.group_analytic_accounting" domain="[('type','not in',('view','template'))]"/>
<field name="product_qty" 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,context)"/>
<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,context)"/>
<field name="price_unit"/>
diff --git a/addons/purchase_requisition/purchase_requisition_view.xml b/addons/purchase_requisition/purchase_requisition_view.xml
index 230c81a0bfc..55427de6573 100644
--- a/addons/purchase_requisition/purchase_requisition_view.xml
+++ b/addons/purchase_requisition/purchase_requisition_view.xml
@@ -33,7 +33,7 @@
<button name="tender_in_progress" states="draft" string="Send to Suppliers" type="object" class="oe_highlight"/>
<button name="tender_reset" states="done,cancel" string="Reset to Draft" type="object" />
<button name="tender_done" states="in_progress" string="Purchase Done" type="object" class="oe_highlight"/>
- <button name="tender_cancel" states="draft,in_progress" string="Cancel" type="object" />
+ <button name="tender_cancel" states="draft,in_progress" string="Cancel Requisition" type="object" />
<field name="state" widget="statusbar" statusbar_visible="draft,in_progress,done" statusbar_colors='{"in_progress":"blue"}'/>
</header>
<sheet>
diff --git a/addons/sale/sale.py b/addons/sale/sale.py
index e71199ab30e..09ee8d5e3fd 100644
--- a/addons/sale/sale.py
+++ b/addons/sale/sale.py
@@ -1020,7 +1020,8 @@ class account_invoice(osv.Model):
sale_order_obj = self.pool.get('sale.order')
res = super(account_invoice, self).confirm_paid(cr, uid, ids, context=context)
so_ids = sale_order_obj.search(cr, uid, [('invoice_ids', 'in', ids)], context=context)
- sale_order_obj.message_post(cr, uid, so_ids, body=_("Invoice paid"), context=context)
+ if so_ids:
+ sale_order_obj.message_post(cr, uid, so_ids, body=_("Invoice paid"), context=context)
return res
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/sale/sale_data.xml b/addons/sale/sale_data.xml
index a0e93cbf581..fc2287cadc0 100644
--- a/addons/sale/sale_data.xml
+++ b/addons/sale/sale_data.xml
@@ -46,10 +46,10 @@
<!-- Sale-related subtypes for messaging / Chatter -->
<record id="mt_order_sent" model="mail.message.subtype">
- <field name="name">Quotation send</field>
+ <field name="name">Quotation sent</field>
<field name="res_model">sale.order</field>
<field name="default" eval="False"/>
- <field name="description">Quotation send</field>
+ <field name="description">Quotation sent</field>
</record>
<record id="mt_order_confirmed" model="mail.message.subtype">
<field name="name">Sales Order Confirmed</field>
diff --git a/addons/sale/sale_view.xml b/addons/sale/sale_view.xml
index b5f022c7799..e3a393666b4 100644
--- a/addons/sale/sale_view.xml
+++ b/addons/sale/sale_view.xml
@@ -140,9 +140,9 @@
<button name="%(action_view_sale_advance_payment_inv)d" string="Create Invoice"
type="action" states="manual" class="oe_highlight" groups="base.group_user"/>
<button name="copy_quotation" states="cancel" string="New Copy of Quotation" type="object"/>
- <button name="cancel" states="draft,sent" string="Cancel" groups="base.group_user"/>
- <button name="action_cancel" states="manual,progress" string="Cancel" type="object" groups="base.group_user"/>
- <button name="invoice_cancel" states="invoice_except" string="Cancel" groups="base.group_user"/>
+ <button name="cancel" states="draft,sent" string="Cancel Quotation" groups="base.group_user"/>
+ <button name="action_cancel" states="manual,progress" string="Cancel Order" type="object" groups="base.group_user"/>
+ <button name="invoice_cancel" states="invoice_except" string="Cancel Order" groups="base.group_user"/>
<field name="state" widget="statusbar" statusbar_visible="draft,sent,invoiced,done" statusbar_colors='{"invoice_except":"red","waiting_date":"blue"}'/>
</header>
<sheet>
@@ -274,7 +274,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
- <field name="message_ids" widget="mail_thread" placeholder="Share a message..."/>
+ <field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
@@ -430,7 +430,7 @@
<form string="Sales Order Lines" version="7.0">
<header>
<button name="%(action_view_sale_order_line_make_invoice)d" string="Create Invoice" type="action" attrs="{'invisible': ['|',('invoiced', '=', 1), ('state', 'not in', ('confirmed', 'draft'))]}" class="oe_highlight" groups="base.group_user"/>
- <button name="button_cancel" string="Cancel" type="object" states="confirmed,exception" groups="base.group_user"/>
+ <button name="button_cancel" string="Cancel Line" type="object" states="confirmed,exception" groups="base.group_user"/>
<button name="button_done" string="Done" type="object" attrs="{'invisible': ['|',('invoiced', '=', 0), ('state', 'not in', ('confirmed', 'exception'))]}" class="oe_highlight" groups="base.group_user"/>
<field name="state" widget="statusbar" statusbar_visible="draft,confirmed,done" statusbar_colors='{"exception":"red","cancel":"red"}'/>
</header>
diff --git a/addons/sale_stock/sale_stock_view.xml b/addons/sale_stock/sale_stock_view.xml
index bebb95f0088..9d852405b9e 100644
--- a/addons/sale_stock/sale_stock_view.xml
+++ b/addons/sale_stock/sale_stock_view.xml
@@ -43,7 +43,7 @@
attrs="{'invisible': ['|','|','|',('picking_ids','=',False),('picking_ids','=',[]), ('state', 'not in', ('progress','manual')),('shipped','=',True)]}" groups="base.group_user"/>
</xpath>
<xpath expr="//button[@name='action_cancel']" position="after">
- <button name="ship_cancel" states="shipping_except" string="Cancel"/>
+ <button name="ship_cancel" states="shipping_except" string="Cancel Order"/>
</xpath>
<field name="state" position="replace">
<field name="state" widget="statusbar" statusbar_visible="draft,sent,progress,invoiced,done" statusbar_colors='{"shipping_except":"red","invoice_except":"red","waiting_date":"blue"}'/>
@@ -105,5 +105,31 @@
</field>
</record>
+ <record id="view_res_partner_tree_type" model="ir.ui.view">
+ <field name="name">res.partner.tree.inherit.type</field>
+ <field name="model">res.partner</field>
+ <field name="inherit_id" ref="base.view_partner_tree"/>
+ <field name="arch" type="xml">
+ <field name="parent_id" position="after">
+ <field name="type" invisible="context.get('hide_type', 1)"/>
+ </field>
+ </field>
+ </record>
+
+ <!-- On the customer/supplier form if "Allow a different address for
+ delivery and invoicing" is set add "Contact Details" in the more menu
+ showing the list of contact with their types -->
+ <act_window
+ id="res_partner_rule_children"
+ name="Contact Details"
+ context="{'default_parent_id': active_id, 'hide_type': 0}"
+ domain="[('parent_id','=',active_id)]"
+ res_model="res.partner"
+ src_model="res.partner"
+ view_mode="tree,form,kanban"
+ view_type="form"
+ groups="sale.group_delivery_invoice_address"
+ />
+
</data>
</openerp>
diff --git a/addons/share/wizard/share_wizard.py b/addons/share/wizard/share_wizard.py
index aae13f66882..429a1dc2f08 100644
--- a/addons/share/wizard/share_wizard.py
+++ b/addons/share/wizard/share_wizard.py
@@ -614,8 +614,8 @@ class share_wizard(osv.TransientModel):
# other groups, so we duplicate if needed
rule = self._check_personal_rule_or_duplicate(cr, group_id, rule, context=context)
eval_ctx = rule_obj._eval_context_for_combinations()
- org_domain = expression.normalize(eval(rule.domain_force, eval_ctx))
- new_clause = expression.normalize(eval(domain, eval_ctx))
+ org_domain = expression.normalize_domain(eval(rule.domain_force, eval_ctx))
+ new_clause = expression.normalize_domain(eval(domain, eval_ctx))
combined_domain = expression.AND([new_clause, org_domain])
rule.write({'domain_force': combined_domain, 'name': rule.name + _('(Modified)')})
_logger.debug("Combining sharing rule %s on model %s with domain: %s", rule.id, model_id, domain)
@@ -660,7 +660,7 @@ class share_wizard(osv.TransientModel):
self._assert(wizard_data.action_id and wizard_data.access_mode,
_('Action and Access Mode are required to create a shared access.'),
context=context)
- self._assert(self.has_share(cr, uid, context=context),
+ self._assert(self.has_share(cr, uid, wizard_data, context=context),
_('You must be a member of the Share/User group to use the share wizard.'),
context=context)
if wizard_data.user_type == 'emails':
diff --git a/addons/stock/report/picking.rml b/addons/stock/report/picking.rml
index ecc682dceda..e0788dcb966 100644
--- a/addons/stock/report/picking.rml
+++ b/addons/stock/report/picking.rml
@@ -152,6 +152,7 @@
</blockTable>
</pto_header>
<para style="terp_default_8">[[repeatIn(objects,'picking')]] </para>
+ <para style="terp_default_8">[[picking.type == 'out' and picking.partner_id and setLang(picking.partner_id.lang) or '']]</para>
<para style="terp_default_9">
<font color="white"> </font>
</para>
diff --git a/addons/stock/stock.py b/addons/stock/stock.py
index 8e4611dc5d0..6bf020623f7 100644
--- a/addons/stock/stock.py
+++ b/addons/stock/stock.py
@@ -2391,7 +2391,7 @@ class stock_move(osv.osv):
processing of the given stock move.
"""
# prepare default values considering that the destination accounts have the reference_currency_id as their main currency
- partner_id = (move.picking_id.partner_id and move.picking_id.partner_id.id and move.picking_id.partner_id.id) or False
+ partner_id = (move.picking_id.partner_id and self.pool.get('res.partner')._find_accounting_partner(move.picking_id.partner_id).id) or False
debit_line_vals = {
'name': move.name,
'product_id': move.product_id and move.product_id.id or False,
diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml
index 98e6e626c32..bc47d83dc89 100644
--- a/addons/stock/stock_view.xml
+++ b/addons/stock/stock_view.xml
@@ -448,7 +448,7 @@
<field name="model">stock.move</field>
<field name="field_parent">move_history_ids</field>
<field name="arch" type="xml">
- <tree colors="grey:state == 'cancel'" string="Moves">
+ <tree colors="grey:state == 'cancel'" string="Moves" create="false">
<field name="product_id"/>
<field name="product_qty"/>
<field name="product_uom" string="Unit of Measure" groups="product.group_uom"/>
@@ -471,7 +471,7 @@
<field name="model">stock.move</field>
<field name="field_parent">move_history_ids2</field>
<field name="arch" type="xml">
- <tree colors="grey:state == 'cancel'" string="Moves">
+ <tree colors="grey:state == 'cancel'" string="Moves" create="false">
<field name="product_id"/>
<field name="product_qty"/>
<field name="product_uom" string="Unit of Measure" groups="product.group_uom"/>
@@ -764,7 +764,7 @@
<button name="action_process" states="assigned" string="Confirm &amp; Transfer" groups="stock.group_stock_user" type="object" class="oe_highlight"/>
<button name="%(action_stock_invoice_onshipping)d" string="Create Invoice/Refund" attrs="{'invisible': ['|','|',('state','&lt;&gt;','done'),('invoice_state','=','invoiced'),('invoice_state','=','none')]}" type="action" class="oe_highlight" groups="base.group_user"/>
<button name="%(act_stock_return_picking)d" string="Reverse Transfer" states="done" type="action" groups="base.group_user"/>
- <button name="button_cancel" states="assigned,confirmed,draft" string="_Cancel" groups="base.group_user"/>
+ <button name="button_cancel" states="assigned,confirmed,draft" string="Cancel Transfer" groups="base.group_user"/>
<field name="state" widget="statusbar" statusbar_visible="draft,assigned,done" statusbar_colors='{"shipping_except":"red","invoice_except":"red","waiting_date":"blue"}'/>
</header>
<sheet>
@@ -1177,6 +1177,10 @@
<field name="product_qty" on_change="onchange_quantity(product_id, product_qty, product_uom, product_uos)"/>
<field name="product_uom" string="Unit of Measure" groups="product.group_uom"/>
<field name="product_uos" groups="product.group_uos"/>
+ <field name="location_id" groups="stock.group_locations" invisible="1"/>
+ <field name="picking_id" invisible="1" />
+ <field name="create_date" invisible="1" />
+ <field name="date_expected" invisible="1" />
<button name="%(stock.move_scrap)d"
string="Scrap Products" type="action"
icon="terp-gtk-jump-to-ltr" context="{'scrap': True}"
@@ -1217,7 +1221,7 @@
<button name="%(action_partial_move_server)d" string="Process Partially" type="action" states="assigned" class="oe_highlight"/>
<button name="action_done" states="draft,assigned,confirmed" string="Process Entirely" type="object" class="oe_highlight"/>
<button name="force_assign" states="confirmed" string="Set Available" type="object" class="oe_highlight"/>
- <button name="action_cancel" states="assigned,confirmed" string="_Cancel" type="object"/>
+ <button name="action_cancel" states="assigned,confirmed" string="Cancel Move" type="object"/>
<field name="state" widget="statusbar" statusbar_visible="draft,confirmed,assigned,done" statusbar_colors='{"waiting":"blue","confirmed":"blue"}'/>
</header>
<sheet>
@@ -1366,7 +1370,7 @@
<field eval="3" name="priority"/>
<field name="arch" type="xml">
<search string="Stock Moves">
- <field name="origin" domain="['|',('origin','ilike',self),('picking_id','ilike',self)]" string="Reference"/>
+ <field name="origin" filter_domain="['|',('origin','ilike',self),('picking_id','ilike',self)]" string="Reference"/>
<field name="date" groups="base.group_no_one"/>
<filter icon="terp-camera_test" string="Ready" name="ready" domain="[('state','=','assigned')]" help="Stock moves that are Available (Ready to process)"/>
@@ -1383,8 +1387,8 @@
<filter string="Picking" name="groupby_picking_id" icon="terp-accessories-archiver" domain="[]" context="{'group_by':'picking_id'}"/>
<filter string="Serial Number" name="groupby_prodlot_id" icon="terp-accessories-archiver" domain="[]" context="{'group_by':'prodlot_id'}"/>
<filter string="Pack" name="groupby_tracking_id" icon="terp-accessories-archiver" domain="[]" context="{'group_by':'tracking_id'}"/>
- <filter string="Source" name="groupby_location_id" icon="terp-gtk-jump-to-rtl" domain="[]" context="{'group_by':'location_id'}"/>
- <filter string="Destination" name="groupby_dest_location_id" icon="terp-gtk-jump-to-ltr" domain="[]" context="{'group_by':'location_dest_id'}"/>
+ <filter string="Source" name="groupby_location_id" icon="terp-gtk-jump-to-rtl" domain="[]" context="{'group_by':'location_id'}" groups="stock.group_locations"/>
+ <filter string="Destination" name="groupby_dest_location_id" icon="terp-gtk-jump-to-ltr" domain="[]" context="{'group_by':'location_dest_id'}" groups="stock.group_locations"/>
<filter icon="terp-stock_effects-object-colorize" string="Status" domain="[]" context="{'group_by':'state'}"/>
<filter string="Creation" name="groupby_create_date" icon="terp-go-month" domain="[]" context="{'group_by':'create_date'}" groups="base.group_no_one"/>
<filter string="Scheduled" name="groupby_date" icon="terp-go-month" domain="[]" context="{'group_by':'date_expected'}"/>
@@ -1448,6 +1452,7 @@
<field name="location_id" invisible="1"/>
<field name="location_dest_id" invisible="1"/>
<field name="create_date" invisible="1"/>
+ <field name="date_expected" invisible="1"/>
<button name="%(stock.move_scrap)d"
string="Scrap Products" type="action"
icon="terp-gtk-jump-to-ltr" context="{'scrap': True}"
diff --git a/addons/survey/survey_view.xml b/addons/survey/survey_view.xml
index 7da3f26fc2f..f41d257cf63 100644
--- a/addons/survey/survey_view.xml
+++ b/addons/survey/survey_view.xml
@@ -23,7 +23,7 @@
<header>
<button name="survey_open" string="Open" states="cancel,close" type="object"/>
<button name="survey_close" string="Close" states="open" type="object" class="oe_highlight"/>
- <button name="survey_cancel" string="Cancel" states="open" type="object"/>
+ <button name="survey_cancel" string="Cancel Survey" states="open" type="object"/>
<field name="state" widget="statusbar" statusbar_visible="open,close"/>
</header>
<sheet>
@@ -1117,7 +1117,7 @@
<button name="survey_req_draft" string="Set to Draft" states="cancel,done" type="object"/>
<button name="survey_req_waiting_answer" string="Waiting Answer" states="draft" type="object" class="oe_highlight"/>
<button name="survey_req_done" string="Done" states="waiting_answer" type="object" class="oe_highlight"/>
- <button name="survey_req_cancel" string="Cancel" states="waiting_answer" type="object"/>
+ <button name="survey_req_cancel" string="Cancel Request" states="waiting_answer" type="object"/>
<field name="state" widget="statusbar" statusbar_visible="draft,waiting_answer"/>
</header>
<sheet string="Survey Request">