diff --git a/addons/account/account.py b/addons/account/account.py index 7f08538b97d..3c7c14737c1 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -1447,6 +1447,8 @@ class account_move(osv.osv): def unlink(self, cr, uid, ids, context=None, check=True): if context is None: context = {} + if isinstance(ids, (int, long)): + ids = [ids] toremove = [] obj_move_line = self.pool.get('account.move.line') for move in self.browse(cr, uid, ids, context=context): diff --git a/addons/account/account_move_line.py b/addons/account/account_move_line.py index 0fc9d9e2227..ed8fb8185e7 100644 --- a/addons/account/account_move_line.py +++ b/addons/account/account_move_line.py @@ -311,13 +311,13 @@ class account_move_line(osv.osv): context = {} c = context.copy() c['initital_bal'] = True - sql = """SELECT l2.id, SUM(l1.debit-l1.credit) - FROM account_move_line l1, account_move_line l2 - WHERE l2.account_id = l1.account_id - AND l1.id <= l2.id - AND l2.id IN %s AND """ + \ - self._query_get(cr, uid, obj='l1', context=c) + \ - " GROUP BY l2.id" + sql = """SELECT l1.id, COALESCE(SUM(l2.debit-l2.credit), 0) + FROM account_move_line l1 LEFT JOIN account_move_line l2 + ON (l1.account_id = l2.account_id + AND l2.id <= l1.id + AND """ + \ + self._query_get(cr, uid, obj='l2', context=c) + \ + ") WHERE l1.id IN %s GROUP BY l1.id" cr.execute(sql, [tuple(ids)]) return dict(cr.fetchall()) diff --git a/addons/account/data/account_data.xml b/addons/account/data/account_data.xml index 9d2e65a3a4c..8119e9f1002 100644 --- a/addons/account/data/account_data.xml +++ b/addons/account/data/account_data.xml @@ -16,7 +16,6 @@ - Immediate Payment balance diff --git a/addons/account/edi/invoice_action_data.xml b/addons/account/edi/invoice_action_data.xml index b3fe8362b8b..417169fa8cb 100644 --- a/addons/account/edi/invoice_action_data.xml +++ b/addons/account/edi/invoice_action_data.xml @@ -22,7 +22,7 @@ Invoice - Send by Email - ${object.user_id.email or object.company_id.email or 'noreply@localhost'} + ${(object.user_id.email or object.company_id.email or 'noreply@localhost')|safe} ${object.company_id.name} Invoice (Ref ${object.number or 'n/a'}) ${object.partner_id.id} diff --git a/addons/account/partner.py b/addons/account/partner.py index d0db2ed4f6c..f24ffcf55b4 100644 --- a/addons/account/partner.py +++ b/addons/account/partner.py @@ -107,14 +107,15 @@ class res_partner(osv.osv): _description = 'Partner' def _credit_debit_get(self, cr, uid, ids, field_names, arg, context=None): - query = self.pool.get('account.move.line')._query_get(cr, uid, context=context) + ctx = context.copy() + ctx['all_fiscalyear'] = True + query = self.pool.get('account.move.line')._query_get(cr, uid, context=ctx) cr.execute("""SELECT l.partner_id, a.type, SUM(l.debit-l.credit) FROM account_move_line l LEFT JOIN account_account a ON (l.account_id=a.id) WHERE a.type IN ('receivable','payable') AND l.partner_id IN %s - AND (l.reconcile_id IS NULL OR - reconcile_id in (SELECT id FROM account_move_reconcile WHERE opening_reconciliation is TRUE)) + AND l.reconcile_id IS NULL AND """ + query + """ GROUP BY l.partner_id, a.type """, diff --git a/addons/account/report/account_balance.rml b/addons/account/report/account_balance.rml index 6ddfc238bff..08c05c65fa8 100644 --- a/addons/account/report/account_balance.rml +++ b/addons/account/report/account_balance.rml @@ -170,6 +170,7 @@ + diff --git a/addons/account/wizard/account_invoice_refund.py b/addons/account/wizard/account_invoice_refund.py index 4b7a7fd5314..8a583b79383 100644 --- a/addons/account/wizard/account_invoice_refund.py +++ b/addons/account/wizard/account_invoice_refund.py @@ -52,10 +52,19 @@ class account_invoice_refund(osv.osv_memory): journal = obj_journal.search(cr, uid, [('type', '=', type), ('company_id','=',company_id)], limit=1, context=context) return journal and journal[0] or False + def _get_reason(self, cr, uid, context=None): + active_id = context and context.get('active_id', False) + if active_id: + inv = self.pool.get('account.invoice').browse(cr, uid, active_id, context=context) + return inv.name + else: + return '' + _defaults = { 'date': lambda *a: time.strftime('%Y-%m-%d'), 'journal_id': _get_journal, 'filter_refund': 'refund', + 'description': _get_reason, } def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False): diff --git a/addons/account_analytic_analysis/account_analytic_analysis_cron.xml b/addons/account_analytic_analysis/account_analytic_analysis_cron.xml index 970ab275ef9..492278a87b5 100644 --- a/addons/account_analytic_analysis/account_analytic_analysis_cron.xml +++ b/addons/account_analytic_analysis/account_analytic_analysis_cron.xml @@ -4,9 +4,9 @@ Contract expiration reminder - ${object.email or ''} + ${(object.email or '')|safe} Contract expiration reminder ${user.company_id.name} - ${object.email} + ${object.email|safe} ${object.lang} diff --git a/addons/account_budget/report/analytic_account_budget_report.rml b/addons/account_budget/report/analytic_account_budget_report.rml index 837825aa4e3..15fddebd02d 100644 --- a/addons/account_budget/report/analytic_account_budget_report.rml +++ b/addons/account_budget/report/analytic_account_budget_report.rml @@ -182,19 +182,19 @@ - [['.....' *(a['status']-1) ]] [[ (a['status']==1 and (setTag('para','para',{'fontName':'Helvetica-bold'}))) or removeParentNode('font') ]] [[ a['name'] ]] + [['.....' *(a['status']-1) ]] [[ (a['status']==1 and (setTag('para','para',{'fontName':'Helvetica-Bold'}))) or removeParentNode('font') ]] [[ a['name'] ]] - [[ (a['status']==1 and ( setTag('para','para',{'fontName':'Helvetica-bold'}))) or removeParentNode('font') ]] [[ formatLang(a['theo'], currency_obj=company.currency_id) ]] + [[ (a['status']==1 and ( setTag('para','para',{'fontName':'Helvetica-Bold'}))) or removeParentNode('font') ]] [[ formatLang(a['theo'], currency_obj=company.currency_id) ]] - [[ (a['status']==1 and ( setTag('para','para',{'fontName':'Helvetica-bold'}))) or removeParentNode('font') ]] [[ formatLang(a['pln'], currency_obj=company.currency_id) ]] + [[ (a['status']==1 and ( setTag('para','para',{'fontName':'Helvetica-Bold'}))) or removeParentNode('font') ]] [[ formatLang(a['pln'], currency_obj=company.currency_id) ]] - [[ (a['status']==1 and ( setTag('para','para',{'fontName':'Helvetica-bold'}))) or removeParentNode('font') ]] [[ formatLang(a['prac'], currency_obj=company.currency_id) ]] + [[ (a['status']==1 and ( setTag('para','para',{'fontName':'Helvetica-Bold'}))) or removeParentNode('font') ]] [[ formatLang(a['prac'], currency_obj=company.currency_id) ]] - [[ (a['status']==1 and ( setTag('para','para',{'fontName':'Helvetica-bold'}))) or removeParentNode('font') ]] [[ formatLang(a['perc']) ]]% + [[ (a['status']==1 and ( setTag('para','para',{'fontName':'Helvetica-Bold'}))) or removeParentNode('font') ]] [[ formatLang(a['perc']) ]]% diff --git a/addons/account_budget/report/budget_report.rml b/addons/account_budget/report/budget_report.rml index 8b9835b6615..789c3ed1ffb 100644 --- a/addons/account_budget/report/budget_report.rml +++ b/addons/account_budget/report/budget_report.rml @@ -145,19 +145,19 @@ - [['.....' *(a['status']-1) ]][[ (a['status']==1 and (setTag('para','para',{'fontName':'Helvetica-bold'}))) or removeParentNode('font') ]] [[ a['name'] ]] + [['.....' *(a['status']-1) ]][[ (a['status']==1 and (setTag('para','para',{'fontName':'Helvetica-Bold'}))) or removeParentNode('font') ]] [[ a['name'] ]] - [[ (a['status']==1 and ( setTag('para','para',{'fontName':'Helvetica-bold'}))) or removeParentNode('font') ]] [[ formatLang(a['theo'], digits=get_digits(dp='Account'), currency_obj=company.currency_id) ]] + [[ (a['status']==1 and ( setTag('para','para',{'fontName':'Helvetica-Bold'}))) or removeParentNode('font') ]] [[ formatLang(a['theo'], digits=get_digits(dp='Account'), currency_obj=company.currency_id) ]] - [[ (a['status']==1 and ( setTag('para','para',{'fontName':'Helvetica-bold'}))) or removeParentNode('font') ]] [[ formatLang(a['pln'], digits=get_digits(dp='Account'), currency_obj=company.currency_id) ]] + [[ (a['status']==1 and ( setTag('para','para',{'fontName':'Helvetica-Bold'}))) or removeParentNode('font') ]] [[ formatLang(a['pln'], digits=get_digits(dp='Account'), currency_obj=company.currency_id) ]] - [[ (a['status']==1 and ( setTag('para','para',{'fontName':'Helvetica-bold'}))) or removeParentNode('font') ]] [[ formatLang(a['prac'], digits=get_digits(dp='Account'), currency_obj=company.currency_id) ]] + [[ (a['status']==1 and ( setTag('para','para',{'fontName':'Helvetica-Bold'}))) or removeParentNode('font') ]] [[ formatLang(a['prac'], digits=get_digits(dp='Account'), currency_obj=company.currency_id) ]] - [[ (a['status']==1 and ( setTag('para','para',{'fontName':'Helvetica-bold'}))) or removeParentNode('font') ]] [[ formatLang(a['perc'], digits=2) ]]% + [[ (a['status']==1 and ( setTag('para','para',{'fontName':'Helvetica-Bold'}))) or removeParentNode('font') ]] [[ formatLang(a['perc'], digits=2) ]]% diff --git a/addons/account_budget/report/crossovered_budget_report.rml b/addons/account_budget/report/crossovered_budget_report.rml index 50b7d267125..a63920a4c9f 100644 --- a/addons/account_budget/report/crossovered_budget_report.rml +++ b/addons/account_budget/report/crossovered_budget_report.rml @@ -160,19 +160,19 @@ - [['.....' *(a['status']-1) ]][[ (a['status']==1 and (setTag('para','para',{'fontName':'Helvetica-bold'}))) or removeParentNode('font') ]] [[ a['name'] ]] + [['.....' *(a['status']-1) ]][[ (a['status']==1 and (setTag('para','para',{'fontName':'Helvetica-Bold'}))) or removeParentNode('font') ]] [[ a['name'] ]] - [[ (a['status']==1 and ( setTag('para','para',{'fontName':'Helvetica-bold'}))) or removeParentNode('font') ]] [[ formatLang(a['theo'], dp='Account', currency_obj=company.currency_id) ]] + [[ (a['status']==1 and ( setTag('para','para',{'fontName':'Helvetica-Bold'}))) or removeParentNode('font') ]] [[ formatLang(a['theo'], dp='Account', currency_obj=company.currency_id) ]] - [[ (a['status']==1 and ( setTag('para','para',{'fontName':'Helvetica-bold'}))) or removeParentNode('font') ]] [[ formatLang(a['pln'], dp='Account', currency_obj=company.currency_id) ]] + [[ (a['status']==1 and ( setTag('para','para',{'fontName':'Helvetica-Bold'}))) or removeParentNode('font') ]] [[ formatLang(a['pln'], dp='Account', currency_obj=company.currency_id) ]] - [[ (a['status']==1 and ( setTag('para','para',{'fontName':'Helvetica-bold'}))) or removeParentNode('font') ]] [[ formatLang(a['prac'], dp='Account', currency_obj=company.currency_id) ]] + [[ (a['status']==1 and ( setTag('para','para',{'fontName':'Helvetica-Bold'}))) or removeParentNode('font') ]] [[ formatLang(a['prac'], dp='Account', currency_obj=company.currency_id) ]] - [[ (a['status']==1 and ( setTag('para','para',{'fontName':'Helvetica-bold'}))) or removeParentNode('font') ]] [[ formatLang(a['perc'],digits=2) ]]% + [[ (a['status']==1 and ( setTag('para','para',{'fontName':'Helvetica-Bold'}))) or removeParentNode('font') ]] [[ formatLang(a['perc'],digits=2) ]]% diff --git a/addons/account_followup/account_followup_data.xml b/addons/account_followup/account_followup_data.xml index ec18e0d2b0c..21c638fa513 100644 --- a/addons/account_followup/account_followup_data.xml +++ b/addons/account_followup/account_followup_data.xml @@ -6,9 +6,9 @@ First polite payment follow-up reminder email - ${user.email or ''} + ${(user.email or '')|safe} ${user.company_id.name} Payment Reminder - ${object.email} + ${object.email|safe} ${object.lang} @@ -45,9 +45,9 @@ ${object.get_followup_table_html() | safe} A bit urging second payment follow-up reminder email - ${user.email or ''} + ${(user.email or '')|safe} ${user.company_id.name} Payment Reminder - ${object.email} + ${object.email|safe} ${object.lang} @@ -85,9 +85,9 @@ ${object.get_followup_table_html() | safe} Urging payment follow-up reminder email - ${user.email or ''} + ${(user.email or '')|safe} ${user.company_id.name} Payment Reminder - ${object.email} + ${object.email|safe} ${object.lang} @@ -122,9 +122,9 @@ ${object.get_followup_table_html() | safe} Default payment follow-up reminder e-mail - ${user.email or ''} + ${(user.email or '')|safe} ${user.company_id.name} Payment Reminder - ${object.email} + ${object.email|safe} ${object.lang} @@ -162,7 +162,7 @@ ${object.get_followup_table_html() | safe} 0 15 - True + True Dear %(partner_name)s, diff --git a/addons/auth_oauth/controllers/main.py b/addons/auth_oauth/controllers/main.py index bedf32ae776..af8b36d2556 100644 --- a/addons/auth_oauth/controllers/main.py +++ b/addons/auth_oauth/controllers/main.py @@ -19,7 +19,7 @@ _logger = logging.getLogger(__name__) #---------------------------------------------------------- def fragment_to_query_string(func): @functools.wraps(func) - def wrapper(self, **kw): + def wrapper(self, *a, **kw): if not kw: return """""" - return func(self, **kw) + return func(self, *a, **kw) return wrapper diff --git a/addons/auth_signup/auth_signup_data.xml b/addons/auth_signup/auth_signup_data.xml index 1b4e3e05502..65ada8f39db 100644 --- a/addons/auth_signup/auth_signup_data.xml +++ b/addons/auth_signup/auth_signup_data.xml @@ -22,8 +22,8 @@ Reset Password - ]]> - ${object.email} + ]]> + ${object.email|safe} Password reset A password reset was requested for the OpenERP account linked to this email.

@@ -37,8 +37,8 @@ OpenERP Enterprise Connection - ]]> - ${object.email} + ]]> + ${object.email|safe} ). -# Copyright (C) 2010-2012 OpenERP s.a. (). +# Copyright (C) 2010-2013 OpenERP s.a. (). # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -49,6 +49,7 @@ class board_board(osv.osv): ('value', 'in', refs), ], context=context) menu_ids = map(itemgetter('res_id'), IrValues.read(cr, uid, irv_ids, ['res_id'], context=context)) + menu_ids = Menus._filter_visible_menus(cr, uid, menu_ids, context=context) menu_names = Menus.name_get(cr, uid, menu_ids, context=context) return [dict(id=m[0], name=m[1]) for m in menu_names] diff --git a/addons/board/controllers.py b/addons/board/controllers.py index 50148977216..93634627352 100644 --- a/addons/board/controllers.py +++ b/addons/board/controllers.py @@ -1,22 +1,22 @@ # -*- coding: utf-8 -*- from xml.etree import ElementTree -import openerp from openerp.addons.web.controllers.main import load_actions_from_ir_values +from openerp.addons.web.http import Controller, route, request -class Board(openerp.http.Controller): - @openerp.http.route('/board/add_to_dashboard', type='json', auth='user') +class Board(Controller): + @route('/board/add_to_dashboard', type='json', auth='user') def add_to_dashboard(self, menu_id, action_id, context_to_save, domain, view_mode, name=''): - req = openerp.http.request # FIXME move this method to board.board model - dashboard_action = load_actions_from_ir_values('action', 'tree_but_open', [('ir.ui.menu', menu_id)], False) + dashboard_action = load_actions_from_ir_values('action', 'tree_but_open', + [('ir.ui.menu', menu_id)], False) if dashboard_action: action = dashboard_action[0][2] if action['res_model'] == 'board.board' and action['views'][0][1] == 'form': # Maybe should check the content instead of model board.board ? view_id = action['views'][0][0] - board = req.session.model(action['res_model']).fields_view_get(view_id, 'form') + board = request.session.model(action['res_model']).fields_view_get(view_id, 'form') if board and 'arch' in board: xml = ElementTree.fromstring(board['arch']) column = xml.find('./board/column') @@ -30,10 +30,10 @@ class Board(openerp.http.Controller): }) column.insert(0, new_action) arch = ElementTree.tostring(xml, 'utf-8') - return req.session.model('ir.ui.view.custom').create({ - 'user_id': req.session._uid, + return request.session.model('ir.ui.view.custom').create({ + 'user_id': request.session.uid, 'ref_id': view_id, 'arch': arch - }, req.context) + }, request.context) return False diff --git a/addons/crm/board_crm_view.xml b/addons/crm/board_crm_view.xml index 6bbd450ca38..8bbbece18cb 100644 --- a/addons/crm/board_crm_view.xml +++ b/addons/crm/board_crm_view.xml @@ -20,10 +20,7 @@ graph,tree,form - ['|', - '!', '&', ('probability', '=', 100), ('stage_id.on_change', '=', 1), - '!', '&', ('probability', '=', 0), ('stage_id.sequence', '!=', 1), - ('type', '=', 'opportunity')] + ['&', ('stage_id.fold', '=', False), ('type', '=', 'opportunity')] {'search_default_Stage':1} @@ -48,7 +45,7 @@ graph,tree,form - ['!', '&', ('probability', '=', 0), ('stage_id.sequence', '!=', 1)] + ['|', ('stage_id.fold', '=', False), ('stage_id.probability', '=', 100)] {'search_default_user': 1, 'search_default_Stage': 1}
diff --git a/addons/crm/crm.py b/addons/crm/crm.py index 98581b91666..a98fcadb959 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -68,8 +68,9 @@ class crm_case_stage(osv.osv): help="Link between stages and sales teams. When set, this limitate the current stage to the selected sales teams."), 'case_default': fields.boolean('Default to New Sales Team', help="If you check this field, this stage will be proposed by default on each sales team. It will not assign this stage to existing teams."), - 'fold': fields.boolean('Fold by Default', - help="This stage is not visible, for example in status bar or kanban view, when there are no records in that stage to display."), + 'fold': fields.boolean('Folded in Kanban View', + help='This stage is folded in the kanban view when' + 'there are no records in that stage to display.'), 'type': fields.selection([('lead', 'Lead'), ('opportunity', 'Opportunity'), ('both', 'Both')], @@ -78,8 +79,8 @@ class crm_case_stage(osv.osv): } _defaults = { - 'sequence': lambda *args: 1, - 'probability': lambda *args: 0.0, + 'sequence': 1, + 'probability': 0.0, 'on_change': True, 'fold': False, 'type': 'both', diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index c580fd728e2..eaf51293355 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -72,10 +72,11 @@ class crm_lead(format_address, osv.osv): _track = { 'stage_id': { - 'crm.mt_lead_create': lambda self, cr, uid, obj, ctx=None: obj.probability == 0 and obj.stage_id and obj.stage_id.sequence == 1, - 'crm.mt_lead_stage': lambda self, cr, uid, obj, ctx=None: (obj.stage_id and obj.stage_id.sequence != 1) and obj.probability < 100, - 'crm.mt_lead_won': lambda self, cr, uid, obj, ctx=None: obj.probability == 100 and obj.stage_id and obj.stage_id.on_change, - 'crm.mt_lead_lost': lambda self, cr, uid, obj, ctx=None: obj.probability == 0 and obj.stage_id and obj.stage_id.sequence != 1, + # this is only an heuristics; depending on your particular stage configuration it may not match all 'new' stages + 'crm.mt_lead_create': lambda self, cr, uid, obj, ctx=None: obj.probability == 0 and obj.stage_id and obj.stage_id.sequence <= 1, + 'crm.mt_lead_stage': lambda self, cr, uid, obj, ctx=None: (obj.stage_id and obj.stage_id.sequence > 1) and obj.probability < 100, + 'crm.mt_lead_won': lambda self, cr, uid, obj, ctx=None: obj.probability == 100 and obj.stage_id and obj.stage_id.fold, + 'crm.mt_lead_lost': lambda self, cr, uid, obj, ctx=None: obj.probability == 0 and obj.stage_id and obj.stage_id.fold and obj.stage_id.sequence > 1, }, } @@ -93,7 +94,7 @@ class crm_lead(format_address, osv.osv): def _get_default_stage_id(self, cr, uid, context=None): """ Gives default stage_id """ section_id = self._get_default_section_id(cr, uid, context=context) - return self.stage_find(cr, uid, [], section_id, [('sequence', '=', '1')], context=context) + return self.stage_find(cr, uid, [], section_id, [('fold', '=', False)], context=context) def _resolve_section_id_from_context(self, cr, uid, context=None): """ Returns ID of section based on the value of 'section_id' @@ -382,34 +383,52 @@ class crm_lead(format_address, osv.osv): # AND with the domain in parameter search_domain += list(domain) # perform search, return the first found - stage_ids = self.pool.get('crm.case.stage').search(cr, uid, search_domain, order=order, context=context) + stage_ids = self.pool.get('crm.case.stage').search(cr, uid, search_domain, order=order, limit=1, context=context) if stage_ids: return stage_ids[0] return False def case_mark_lost(self, cr, uid, ids, context=None): - """ Mark the case as lost: state=cancel and probability=0 """ - for lead in self.browse(cr, uid, ids): - stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 0.0), ('on_change', '=', True), ('sequence', '>', 1)], context=context) + """ Mark the case as lost: state=cancel and probability=0 + :deprecated: this method will be removed in OpenERP v8. + """ + stages_leads = {} + for lead in self.browse(cr, uid, ids, context=context): + stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 0.0), ('fold', '=', True), ('sequence', '>', 1)], context=context) if stage_id: - return self.write(cr, uid, [lead.id], {'stage_id': stage_id}, context=context) + if stages_leads.get(stage_id): + stages_leads[stage_id].append(lead.id) + else: + stages_leads[stage_id] = [lead.id] else: raise osv.except_osv(_('Warning!'), _('To relieve your sales pipe and group all Lost opportunities, configure one of your sales stage as follow:\n' 'probability = 0 %, select "Change Probability Automatically".\n' 'Create a specific stage or edit an existing one by editing columns of your opportunity pipe.')) + for stage_id, lead_ids in stages_leads.items(): + self.write(cr, uid, lead_ids, {'stage_id': stage_id}, context=context) + return True def case_mark_won(self, cr, uid, ids, context=None): - """ Mark the case as won: state=done and probability=100 """ - for lead in self.browse(cr, uid, ids): - stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 100.0), ('on_change', '=', True), ('sequence', '>', 1)], context=context) + """ Mark the case as won: state=done and probability=100 + :deprecated: this method will be removed in OpenERP v8. + """ + stages_leads = {} + for lead in self.browse(cr, uid, ids, context=context): + stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 100.0), ('fold', '=', True)], context=context) if stage_id: - return self.write(cr, uid, [lead.id], {'stage_id': stage_id}, context=context) + if stages_leads.get(stage_id): + stages_leads[stage_id].append(lead.id) + else: + stages_leads[stage_id] = [lead.id] else: raise osv.except_osv(_('Warning!'), _('To relieve your sales pipe and group all Won opportunities, configure one of your sales stage as follow:\n' 'probability = 100 % and select "Change Probability Automatically".\n' 'Create a specific stage or edit an existing one by editing columns of your opportunity pipe.')) + for stage_id, lead_ids in stages_leads.items(): + self.write(cr, uid, lead_ids, {'stage_id': stage_id}, context=context) + return True def case_escalate(self, cr, uid, ids, context=None): """ Escalates case to parent level """ @@ -617,7 +636,7 @@ class crm_lead(format_address, osv.osv): # An Opportunity always has higher confidence level than a lead, unless its stage probability is 0.0 for opportunity in opportunities: sequence = -1 - if opportunity.stage_id and (opportunity.stage_id.probability != 0 or opportunity.stage_id.sequence == 1): + if opportunity.stage_id and not opportunity.stage_id.fold: sequence = opportunity.stage_id.sequence sequenced_opps.append(((int(sequence != -1 and opportunity.type == 'opportunity'), sequence, -opportunity.id), opportunity)) @@ -678,7 +697,7 @@ class crm_lead(format_address, osv.osv): 'phone': customer and customer.phone or lead.phone, } if not lead.stage_id or lead.stage_id.type=='lead': - val['stage_id'] = self.stage_find(cr, uid, [lead], section_id, [('sequence', '=', '1'), ('type', 'in', ('opportunity','both'))], context=context) + val['stage_id'] = self.stage_find(cr, uid, [lead], section_id, [('type', 'in', ('opportunity', 'both'))], context=context) return val def convert_opportunity(self, cr, uid, ids, partner_id, user_ids=False, section_id=False, context=None): @@ -688,7 +707,7 @@ class crm_lead(format_address, osv.osv): customer = partner.browse(cr, uid, partner_id, context=context) for lead in self.browse(cr, uid, ids, context=context): # TDE: was if lead.state in ('done', 'cancel'): - if (lead.probability == '100') or (lead.probability == '0' and lead.stage_id.sequence != '1'): + if lead.probability == 100 or (lead.probability == 0 and lead.stage_id.fold): continue vals = self._convert_opportunity_data(cr, uid, lead, customer, section_id, context=context) self.write(cr, uid, [lead.id], vals, context=context) @@ -818,9 +837,11 @@ class crm_lead(format_address, osv.osv): model_data = self.pool.get('ir.model.data') phonecall_dict = {} if not categ_id: - res_id = model_data._get_id(cr, uid, 'crm', 'categ_phone2') - if res_id: + try: + res_id = model_data._get_id(cr, uid, 'crm', 'categ_phone2') categ_id = model_data.browse(cr, uid, res_id, context=context).res_id + except ValueError: + pass for lead in self.browse(cr, uid, ids, context=context): if not section_id: section_id = lead.section_id and lead.section_id.id or False @@ -927,6 +948,23 @@ class crm_lead(format_address, osv.osv): vals.update(onchange_stage_values) return super(crm_lead, self).write(cr, uid, ids, vals, context=context) + def copy(self, cr, uid, id, default=None, context=None): + if not default: + default = {} + if not context: + context = {} + lead = self.browse(cr, uid, id, context=context) + local_context = dict(context) + local_context.setdefault('default_type', lead.type) + local_context.setdefault('default_section_id', lead.section_id) + if lead.type == 'opportunity': + default['date_open'] = fields.datetime.now() + else: + default['date_open'] = False + default['date_closed'] = False + default['stage_id'] = self._get_default_stage_id(cr, uid, local_context) + return super(crm_lead, self).copy(cr, uid, id, default, context=context) + # ---------------------------------------- # Mail Gateway # ---------------------------------------- diff --git a/addons/crm/crm_lead_data.xml b/addons/crm/crm_lead_data.xml index 3c299fef704..6bb8e2700d4 100644 --- a/addons/crm/crm_lead_data.xml +++ b/addons/crm/crm_lead_data.xml @@ -44,6 +44,7 @@ Won 1 + 1 100 1 70 @@ -203,7 +204,7 @@ ${object.partner_id != False and object.partner_id.id} - ${not object.partner_id and object.email_from} + ${(not object.partner_id and object.email_from)|safe} @@ -211,7 +212,7 @@ admin@example.com - ${object.user_id != False and object.user_id.email} + ${(object.user_id != False and object.user_id.email)|safe} Reminder on Lead: ${object.id} from ${object.partner_id != False and object.partner_id.name or object.contact_name} This opportunity did not have any activity since at least 5 days. Here are some details:

    diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml index c0eefcc8d4c..f3e424e3e37 100644 --- a/addons/crm/crm_lead_view.xml +++ b/addons/crm/crm_lead_view.xml @@ -323,7 +323,7 @@ crm.lead - + @@ -334,14 +334,14 @@ - - + domain="[('probability', '=', '0'), ('stage_id.fold', '=', True)]"/>
    +
    @@ -550,18 +552,16 @@ - + domain="[('probability', '=', 100), ('stage_id.fold', '=', True)]"/> + domain="[('probability', '=', 0), ('stage_id.fold', '=', True)]"/> - @@ -600,7 +600,7 @@ id="crm.action_lead_mass_mail" context="{ 'default_composition_mode': 'mass_mail', - 'default_email_to':'{$object.email or \'\'}', + 'default_email_to':'{($object.email or \'\')|safe}', 'default_use_template': True, 'default_template_id': ref('crm.email_template_opportunity_mail'), }" diff --git a/addons/crm/crm_phonecall.py b/addons/crm/crm_phonecall.py index 1e67c50a2c3..e74fecc9bdf 100644 --- a/addons/crm/crm_phonecall.py +++ b/addons/crm/crm_phonecall.py @@ -117,9 +117,11 @@ class crm_phonecall(osv.osv): model_data = self.pool.get('ir.model.data') phonecall_dict = {} if not categ_id: - res_id = model_data._get_id(cr, uid, 'crm', 'categ_phone2') - if res_id: + try: + res_id = model_data._get_id(cr, uid, 'crm', 'categ_phone2') categ_id = model_data.browse(cr, uid, res_id, context=context).res_id + except ValueError: + pass for call in self.browse(cr, uid, ids, context=context): if not section_id: section_id = call.section_id and call.section_id.id or False diff --git a/addons/crm/crm_phonecall_view.xml b/addons/crm/crm_phonecall_view.xml index 585286f5456..a027a910520 100644 --- a/addons/crm/crm_phonecall_view.xml +++ b/addons/crm/crm_phonecall_view.xml @@ -120,7 +120,7 @@ + on_change="on_change_partner_id(partner_id)"/> diff --git a/addons/crm/crm_view.xml b/addons/crm/crm_view.xml index d0dcc07fcb0..8cc4b03cb41 100644 --- a/addons/crm/crm_view.xml +++ b/addons/crm/crm_view.xml @@ -93,14 +93,19 @@ - - - - - - - - + + + + + + + + + + + + + diff --git a/addons/crm/wizard/crm_lead_to_opportunity.py b/addons/crm/wizard/crm_lead_to_opportunity.py index 48097944ec4..f239ad739c1 100644 --- a/addons/crm/wizard/crm_lead_to_opportunity.py +++ b/addons/crm/wizard/crm_lead_to_opportunity.py @@ -59,11 +59,11 @@ class crm_lead2opportunity_partner(osv.osv_memory): if partner_id: # Search for opportunities that have the same partner and that arent done or cancelled - ids = lead_obj.search(cr, uid, [('partner_id', '=', partner_id), ('probability', '<', '100')]) + ids = lead_obj.search(cr, uid, [('partner_id', '=', partner_id), '|', ('probability', '=', False), ('probability', '<', '100')]) for id in ids: tomerge.add(id) if email: - ids = lead_obj.search(cr, uid, [('email_from', '=ilike', email[0]), ('probability', '<', '100')]) + ids = lead_obj.search(cr, uid, [('email_from', '=ilike', email[0]), '|', ('probability', '=', False), ('probability', '<', '100')]) for id in ids: tomerge.add(id) diff --git a/addons/crm/wizard/crm_opportunity_to_phonecall.py b/addons/crm/wizard/crm_opportunity_to_phonecall.py index bb1ac2fdce6..5183773b618 100644 --- a/addons/crm/wizard/crm_opportunity_to_phonecall.py +++ b/addons/crm/wizard/crm_opportunity_to_phonecall.py @@ -34,9 +34,11 @@ class crm_opportunity2phonecall(osv.osv_memory): opp_obj = self.pool.get('crm.lead') categ_id = False data_obj = self.pool.get('ir.model.data') - res_id = data_obj._get_id(cr, uid, 'crm', 'categ_phone2') - if res_id: + try: + res_id = data_obj._get_id(cr, uid, 'crm', 'categ_phone2') categ_id = data_obj.browse(cr, uid, res_id, context=context).res_id + except ValueError: + pass record_ids = context and context.get('active_ids', []) or [] res = {} diff --git a/addons/crm/wizard/crm_phonecall_to_phonecall.py b/addons/crm/wizard/crm_phonecall_to_phonecall.py index 7e1da41a29f..804b3c7cb0f 100644 --- a/addons/crm/wizard/crm_phonecall_to_phonecall.py +++ b/addons/crm/wizard/crm_phonecall_to_phonecall.py @@ -78,9 +78,11 @@ class crm_phonecall2phonecall(osv.osv_memory): categ_id = False data_obj = self.pool.get('ir.model.data') - res_id = data_obj._get_id(cr, uid, 'crm', 'categ_phone2') - if res_id: + try: + res_id = data_obj._get_id(cr, uid, 'crm', 'categ_phone2') categ_id = data_obj.browse(cr, uid, res_id, context=context).res_id + except ValueError: + pass if 'name' in fields: res.update({'name': phonecall.name}) diff --git a/addons/crm_claim/crm_claim.py b/addons/crm_claim/crm_claim.py index 1b9cb0b0c50..50cbe4a6c44 100644 --- a/addons/crm_claim/crm_claim.py +++ b/addons/crm_claim/crm_claim.py @@ -173,7 +173,8 @@ class crm_claim(osv.osv): 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"), @@ -185,33 +186,7 @@ class crm_claim(osv.osv): if msg.get('priority'): defaults['priority'] = msg.get('priority') defaults.update(custom_values) - return super(crm_claim,self).message_new(cr, uid, msg, custom_values=defaults, context=context) - - def message_update(self, cr, uid, ids, msg, update_vals=None, context=None): - """ Overrides mail_thread message_update that is called by the mailgateway - through message_process. - This method updates the document according to the email. - """ - if isinstance(ids, (str, int, long)): - ids = [ids] - if update_vals is None: update_vals = {} - - if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES): - update_vals['priority'] = msg.get('priority') - - maps = { - 'cost':'planned_cost', - 'revenue': 'planned_revenue', - 'probability':'probability' - } - for line in msg['body'].split('\n'): - line = line.strip() - res = tools.command_re.match(line) - if res and maps.get(res.group(1).lower()): - key = maps.get(res.group(1).lower()) - update_vals[key] = res.group(2).lower() - - return super(crm_claim,self).message_update(cr, uid, ids, msg, update_vals=update_vals, context=context) + return super(crm_claim, self).message_new(cr, uid, msg, custom_values=defaults, context=context) class res_partner(osv.osv): _inherit = 'res.partner' diff --git a/addons/crm_helpdesk/crm_helpdesk.py b/addons/crm_helpdesk/crm_helpdesk.py index 5f84d45f974..c1cd6d2ff91 100644 --- a/addons/crm_helpdesk/crm_helpdesk.py +++ b/addons/crm_helpdesk/crm_helpdesk.py @@ -127,7 +127,8 @@ class crm_helpdesk(osv.osv): 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"), @@ -138,32 +139,6 @@ class crm_helpdesk(osv.osv): 'partner_id': msg.get('author_id', False), } defaults.update(custom_values) - return super(crm_helpdesk,self).message_new(cr, uid, msg, custom_values=defaults, context=context) - - def message_update(self, cr, uid, ids, msg, update_vals=None, context=None): - """ Overrides mail_thread message_update that is called by the mailgateway - through message_process. - This method updates the document according to the email. - """ - if isinstance(ids, (str, int, long)): - ids = [ids] - if update_vals is None: update_vals = {} - - if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES): - update_vals['priority'] = msg.get('priority') - - maps = { - 'cost':'planned_cost', - 'revenue': 'planned_revenue', - 'probability':'probability' - } - for line in msg['body'].split('\n'): - line = line.strip() - res = tools.command_re.match(line) - if res and maps.get(res.group(1).lower()): - key = maps.get(res.group(1).lower()) - update_vals[key] = res.group(2).lower() - - return super(crm_helpdesk,self).message_update(cr, uid, ids, msg, update_vals=update_vals, context=context) + return super(crm_helpdesk, self).message_new(cr, uid, msg, custom_values=defaults, context=context) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/crm_partner_assign/__openerp__.py b/addons/crm_partner_assign/__openerp__.py index e0f1d78558a..3c75ed35911 100644 --- a/addons/crm_partner_assign/__openerp__.py +++ b/addons/crm_partner_assign/__openerp__.py @@ -21,20 +21,17 @@ { - 'name': 'Partners Geo-Localization', + 'name': 'CRM Geolocation', 'version': '1.0', 'category': 'Customer Relationship Management', 'description': """ -This is the module used by OpenERP SA to redirect customers to its partners, based on geolocalization. +This is the module used by OpenERP SA to redirect customers to its partners, based on geolocation. ====================================================================================================== -You can geolocalize your opportunities by using this module. +This modules lets you geolocate Leads, Opportunities and Partners based on their address. -Use geolocalization when assigning opportunities to partners. -Determine the GPS coordinates according to the address of the partner. - -The most appropriate partner can be assigned. -You can also use the geolocalization without using the GPS coordinates. +Once the coordinates of the Lead/Opportunity is known, they can be automatically assigned +to an appropriate local partner, based on the distance and the weight that was assigned to the partner. """, 'author': 'OpenERP SA', 'depends': ['crm', 'account', 'portal'], diff --git a/addons/crm_partner_assign/crm_lead.py b/addons/crm_partner_assign/crm_lead.py index 37f4b9aedff..d7463355271 100644 --- a/addons/crm_partner_assign/crm_lead.py +++ b/addons/crm_partner_assign/crm_lead.py @@ -42,3 +42,17 @@ class crm_lead(osv.osv): def case_disinterested(self, cr, uid, ids, context=None): return self.get_interested_action(cr, uid, False, context=context) + + def assign_salesman_of_assigned_partner(self, cr, uid, ids, context=None): + salesmans_leads = {} + for lead in self.browse(cr, uid, ids, context=context): + if (lead.stage_id.probability > 0 and lead.stage_id.probability < 100) or lead.stage_id.sequence == 1: + if lead.partner_assigned_id and lead.partner_assigned_id.user_id and lead.partner_assigned_id.user_id != lead.user_id: + salesman_id = lead.partner_assigned_id.user_id.id + if salesmans_leads.get(salesman_id): + salesmans_leads[salesman_id].append(lead.id) + else: + salesmans_leads[salesman_id] = [lead.id] + for salesman_id, lead_ids in salesmans_leads.items(): + salesteam_id = self.on_change_user(cr, uid, lead_ids, salesman_id, context=None)['value'].get('section_id') + self.write(cr, uid, lead_ids, {'user_id': salesman_id, 'section_id': salesteam_id}, context=context) diff --git a/addons/crm_partner_assign/crm_lead_view.xml b/addons/crm_partner_assign/crm_lead_view.xml index 7550396e886..80d82c6c2bf 100644 --- a/addons/crm_partner_assign/crm_lead_view.xml +++ b/addons/crm_partner_assign/crm_lead_view.xml @@ -65,6 +65,9 @@ domain="[]" context="{'group_by':'date_assign'}"/> + + + @@ -131,11 +134,40 @@ + + + + + Assign salesman of assigned partner + + code + + if context.get('active_model') == 'crm.lead': + ids = [] + if context.get('active_domain'): + ids = self.search(cr, uid, context['active_domain'], context=context) + elif context.get('active_ids'): + ids = context['active_ids'] + if ids: + self.assign_salesman_of_assigned_partner(cr, uid, ids, context=context) + + + + + + + Assign salesman of assigned partner + client_action_multi + + action + crm.lead + + diff --git a/addons/crm_partner_assign/crm_partner_assign.py b/addons/crm_partner_assign/crm_partner_assign.py index daf3d844fe6..c5329696b32 100644 --- a/addons/crm_partner_assign/crm_partner_assign.py +++ b/addons/crm_partner_assign/crm_partner_assign.py @@ -179,23 +179,27 @@ class crm_lead(osv.osv): return res def assign_geo_localize(self, cr, uid, ids, latitude=False, longitude=False, context=None): - # Don't pass context to browse()! We need country name in english below - for lead in self.browse(cr, uid, ids): - if not lead.country_id: - continue - result = geo_find(geo_query_address(street=lead.street, - zip=lead.zip, - city=lead.city, - state=lead.state_id.name, - country=lead.country_id.name)) - if not latitude and result: - latitude = result[0] - if not longitude and result: - longitude = result[1] - self.write(cr, uid, [lead.id], { + if latitude and longitude: + self.write(cr, uid, ids, { 'partner_latitude': latitude, 'partner_longitude': longitude }, context=context) + return True + # Don't pass context to browse()! We need country name in english below + for lead in self.browse(cr, uid, ids): + if lead.partner_latitude and lead.partner_longitude: + continue + if lead.country_id: + result = geo_find(geo_query_address(street=lead.street, + zip=lead.zip, + city=lead.city, + state=lead.state_id.name, + country=lead.country_id.name)) + if result: + self.write(cr, uid, [lead.id], { + 'partner_latitude': result[0], + 'partner_longitude': result[1] + }, context=context) return True def search_geo_partner(self, cr, uid, ids, context=None): diff --git a/addons/crm_partner_assign/crm_partner_assign_data.xml b/addons/crm_partner_assign/crm_partner_assign_data.xml index b4347b1621b..284476cb6ee 100644 --- a/addons/crm_partner_assign/crm_partner_assign_data.xml +++ b/addons/crm_partner_assign/crm_partner_assign_data.xml @@ -4,14 +4,14 @@ Assigned - + lead To Recycle - + lead @@ -19,8 +19,8 @@ Lead Mass Mail - ${ctx['partner_id'].email} - ${user.email or ''} + ${ctx['partner_id'].email|safe} + ${(user.email or '')|safe} Fwd: Lead: ${ctx['partner_id'].name} % for lead in ctx['partner_leads']: -
  • ${lead.lead_id.name or 'Subject Undefined'}, ${lead.lead_id.contact_name or 'Contact Name Undefined'}, ${lead.lead_id.country_id and lead.lead_id.country_id.name or 'Country Undefined' }, ${lead.lead_id.email_from or 'Email Undefined'}, ${lead.lead_id.phone or ''}

  • +
  • ${lead.lead_id.name or 'Subject Undefined'}, ${lead.lead_id.partner_name or lead.lead_id.contact_name or 'Contact Name Undefined'}, ${lead.lead_id.country_id and lead.lead_id.country_id.name or 'Country Undefined' }, ${lead.lead_id.email_from or 'Email Undefined'}, ${lead.lead_id.phone or ''}

  • % endfor @@ -56,4 +56,4 @@ PS: It looks like you do not have an account manager assigned to you, please con ]]>
    - \ No newline at end of file + diff --git a/addons/delivery/stock.py b/addons/delivery/stock.py index 95e87eb45ab..42c24b4a27d 100644 --- a/addons/delivery/stock.py +++ b/addons/delivery/stock.py @@ -187,6 +187,9 @@ class stock_move(osv.osv): # Redefinition of the new fields in order to update the model stock.picking.out in the orm # FIXME: this is a temporary workaround because of a framework bug (ref: lp996816). It should be removed as soon as # the bug is fixed + +# TODO in trunk: Remove the duplication below using a mixin class! + class stock_picking_out(osv.osv): _inherit = 'stock.picking.out' @@ -212,6 +215,7 @@ class stock_picking_out(osv.osv): }), 'carrier_tracking_ref': fields.char('Carrier Tracking Ref', size=32), 'number_of_packages': fields.integer('Number of Packages'), + 'weight_uom_id': fields.many2one('product.uom', 'Unit of Measure', required=True,readonly="1",help="Unit of measurement for Weight",), } class stock_picking_in(osv.osv): @@ -224,6 +228,8 @@ class stock_picking_in(osv.osv): return self.pool.get('stock.picking')._get_picking_line(cr, uid, ids, context=context) _columns = { + 'carrier_id':fields.many2one("delivery.carrier","Carrier"), + 'volume': fields.float('Volume'), 'weight': fields.function(_cal_weight, type='float', string='Weight', digits_compute= dp.get_precision('Stock Weight'), multi='_cal_weight', store={ 'stock.picking': (lambda self, cr, uid, ids, c={}: ids, ['move_lines'], 20), @@ -234,6 +240,9 @@ class stock_picking_in(osv.osv): 'stock.picking': (lambda self, cr, uid, ids, c={}: ids, ['move_lines'], 20), 'stock.move': (_get_picking_line, ['product_id','product_qty','product_uom','product_uos_qty'], 20), }), + 'carrier_tracking_ref': fields.char('Carrier Tracking Ref', size=32), + 'number_of_packages': fields.integer('Number of Packages'), + 'weight_uom_id': fields.many2one('product.uom', 'Unit of Measure', required=True,readonly="1",help="Unit of measurement for Weight",), } # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/document/security/ir.model.access.csv b/addons/document/security/ir.model.access.csv index e0513a0640c..8c5e4323831 100644 --- a/addons/document/security/ir.model.access.csv +++ b/addons/document/security/ir.model.access.csv @@ -15,3 +15,4 @@ access_report_document_user_group_document_manager,report.document.user document access_report_document_file_group_document_manager,report.document.file document manager,model_report_document_file,base.group_system,1,0,0,0 access_report_document_file_group_document,report.document.file document manager,model_report_document_file,base.group_document_user,1,0,0,0 access_report_document_user_knowledgeuser,report.document.user knowledgeuser,document.model_report_document_user,base.group_document_user,1,0,0,0 +access_document_storage,access_document_storage,model_document_storage,base.group_system,1,1,1,1 diff --git a/addons/document/static/src/js/document.js b/addons/document/static/src/js/document.js index 23f1375e8c8..e4bce2da76e 100644 --- a/addons/document/static/src/js/document.js +++ b/addons/document/static/src/js/document.js @@ -9,7 +9,7 @@ openerp.document = function (instance) { on_attachments_loaded: function(attachments) { //to display number in name if more then one attachment which has same name. var self = this; - _.chain(attachments.reverse()) + _.chain(attachments) .groupBy(function(attachment) { return attachment.name}) .each(function(attachment){ if(attachment.length > 1) diff --git a/addons/document_ftp/ftpserver/__init__.py b/addons/document_ftp/ftpserver/__init__.py index 3749a02a387..6474def3e99 100644 --- a/addons/document_ftp/ftpserver/__init__.py +++ b/addons/document_ftp/ftpserver/__init__.py @@ -34,6 +34,9 @@ def start_server(): if openerp.multi_process: _logger.info("FTP disabled in multiprocess mode") return + if openerp.evented: + _logger.info("FTP disabled in evented mode") + return HOST = config.get('ftp_server_host', '127.0.0.1') PORT = int(config.get('ftp_server_port', '8021')) PASSIVE_PORTS = None diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py index ed35349f420..efa1387435a 100644 --- a/addons/email_template/email_template.py +++ b/addons/email_template/email_template.py @@ -378,7 +378,7 @@ class email_template(osv.osv): # Ensure report is rendered using template's language ctx = context.copy() if template.lang: - ctx['lang'] = self.render_template_batch(cr, uid, template.lang, template.model, res_id, context) # take 0 ? + ctx['lang'] = self.render_template_batch(cr, uid, template.lang, template.model, [res_id], context)[res_id] # take 0 ? result, format = openerp.report.render_report(cr, uid, [res_id], report_service, {'model': template.model}, ctx) result = base64.b64encode(result) if not report_name: diff --git a/addons/event/email_template.xml b/addons/event/email_template.xml index 95ffbf46d8d..d99774acd3c 100644 --- a/addons/event/email_template.xml +++ b/addons/event/email_template.xml @@ -4,8 +4,8 @@ Confirmation of the Event - ${object.user_id.email or object.company_id.email or 'noreply@' + object.company_id.name + '.com'} - ${object.email} + ${(object.user_id.email or object.company_id.email or 'noreply@' + object.company_id.name + '.com')|safe} + ${object.email|safe} Your registration at ${object.event_id.name} Hello ${object.name},

    @@ -21,8 +21,8 @@ Confirmation of the Registration - ${object.user_id.email or object.company_id.email or 'noreply@' + object.company_id.name + '.com'} - ${object.email} + ${(object.user_id.email or object.company_id.email or 'noreply@' + object.company_id.name + '.com')|safe} + ${object.email|safe} Your registration at ${object.event_id.name} Hello ${object.name},

    diff --git a/addons/fleet/fleet_demo.xml b/addons/fleet/fleet_demo.xml index a550ae78da5..2f9b71b3bb5 100644 --- a/addons/fleet/fleet_demo.xml +++ b/addons/fleet/fleet_demo.xml @@ -1,8 +1,9 @@ + - + diff --git a/addons/fleet/fleet_view.xml b/addons/fleet/fleet_view.xml index 1258e49aa2d..a0ced74a509 100644 --- a/addons/fleet/fleet_view.xml +++ b/addons/fleet/fleet_view.xml @@ -43,6 +43,7 @@
    + fleet.vehicle.model.tree fleet.vehicle.model @@ -565,7 +566,7 @@
    - fleet.vehicle.odometer.graph @@ -671,6 +673,23 @@
    + + + fleet.vehicle.log.fuel.search + fleet.vehicle.log.fuel + + + + + + + + + + + + + fleet.vehicle.log.fuel.graph @@ -690,6 +709,7 @@ fleet.vehicle.log.fuel form tree,form,graph + {"search_default_groupby_vehicle" : True}

    Click to create a new fuel log. diff --git a/addons/hr/hr.py b/addons/hr/hr.py index bf58fdd76f8..37d1bae7fea 100644 --- a/addons/hr/hr.py +++ b/addons/hr/hr.py @@ -236,11 +236,13 @@ class hr_employee(osv.osv): employee_id = super(hr_employee, self).create(cr, uid, data, context=create_ctx) employee = self.browse(cr, uid, employee_id, context=context) if employee.user_id: + res_users = self.pool['res.users'] # send a copy to every user of the company - company_id = employee.user_id.partner_id.company_id.id - partner_ids = self.pool.get('res.partner').search(cr, uid, [ - ('company_id', '=', company_id), - ('user_ids', '!=', False)], context=context) + # TODO: post to the `Whole Company` mail.group when we'll be able to link to the employee record + _model, group_id = self.pool['ir.model.data'].get_object_reference(cr, uid, 'base', 'group_user') + user_ids = res_users.search(cr, uid, [('company_id', '=', employee.user_id.company_id.id), + ('groups_id', 'in', group_id)]) + partner_ids = list(set(u.partner_id.id for u in res_users.browse(cr, uid, user_ids, context=context))) else: partner_ids = [] self.message_post(cr, uid, [employee_id], diff --git a/addons/hr/hr_view.xml b/addons/hr/hr_view.xml index 4922a53bcbf..e9a859bf44d 100644 --- a/addons/hr/hr_view.xml +++ b/addons/hr/hr_view.xml @@ -117,7 +117,7 @@ hr.employee - + diff --git a/addons/hr_expense/hr_expense_view.xml b/addons/hr_expense/hr_expense_view.xml index 7505717c672..33f737aa66e 100644 --- a/addons/hr_expense/hr_expense_view.xml +++ b/addons/hr_expense/hr_expense_view.xml @@ -157,7 +157,7 @@ - + diff --git a/addons/hr_holidays/hr_holidays.py b/addons/hr_holidays/hr_holidays.py index 1af2fc00193..682508e0ca0 100644 --- a/addons/hr_holidays/hr_holidays.py +++ b/addons/hr_holidays/hr_holidays.py @@ -145,7 +145,9 @@ class hr_holidays(osv.osv): def _check_date(self, cr, uid, ids): for holiday in self.browse(cr, uid, ids): - holiday_ids = self.search(cr, uid, [('date_from', '<=', holiday.date_to), ('date_to', '>=', holiday.date_from), ('employee_id', '=', holiday.employee_id.id), ('id', '<>', holiday.id)]) + holiday_ids = self.search(cr, uid, [('date_from', '<=', holiday.date_to), ('date_to', '>=', holiday.date_from), + ('employee_id', '=', holiday.employee_id.id), ('id', '<>', holiday.id), + ('state', 'not in', ['cancel', 'refuse'])]) if holiday_ids: return False return True diff --git a/addons/hr_holidays/hr_holidays_workflow.xml b/addons/hr_holidays/hr_holidays_workflow.xml index b563dbf68d4..c539ff6de41 100644 --- a/addons/hr_holidays/hr_holidays_workflow.xml +++ b/addons/hr_holidays/hr_holidays_workflow.xml @@ -55,6 +55,7 @@ refuse + function holidays_refuse() diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py index 0f0f2785fc1..9d94b079fb5 100644 --- a/addons/hr_recruitment/hr_recruitment.py +++ b/addons/hr_recruitment/hr_recruitment.py @@ -52,12 +52,13 @@ class hr_recruitment_stage(osv.osv): 'name': fields.char('Name', size=64, required=True, translate=True), 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of stages."), 'department_id':fields.many2one('hr.department', 'Specific to a Department', help="Stages of the recruitment process may be different per department. If this stage is common to all departments, keep this field empty."), - 'fold': fields.boolean('Hide in views if empty', help="This stage is not visible, for example in status bar or kanban view, when there are no records in that stage to display."), 'requirements': fields.text('Requirements'), + 'fold': fields.boolean('Folded in Kanban View', + help='This stage is folded in the kanban view when' + 'there are no records in that stage to display.'), } _defaults = { 'sequence': 1, - 'fold': False, } class hr_recruitment_degree(osv.osv): @@ -82,8 +83,9 @@ class hr_applicant(osv.Model): _inherit = ['mail.thread', 'ir.needaction_mixin'] _track = { 'stage_id': { - 'hr_recruitment.mt_applicant_new': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence == 1, - 'hr_recruitment.mt_applicant_stage_changed': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence != 1, + # this is only an heuristics; depending on your particular stage configuration it may not match all 'new' stages + 'hr_recruitment.mt_applicant_new': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence <= 1, + 'hr_recruitment.mt_applicant_stage_changed': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence > 1, }, } @@ -94,7 +96,7 @@ class hr_applicant(osv.Model): def _get_default_stage_id(self, cr, uid, context=None): """ Gives default stage_id """ department_id = self._get_default_department_id(cr, uid, context=context) - return self.stage_find(cr, uid, [], department_id, [('sequence', '=', '1')], context=context) + return self.stage_find(cr, uid, [], department_id, [('fold', '=', False)], context=context) def _resolve_department_id_from_context(self, cr, uid, context=None): """ Returns ID of department based on the value of 'default_department_id' @@ -241,7 +243,7 @@ class hr_applicant(osv.Model): def onchange_department_id(self, cr, uid, ids, department_id=False, stage_id=False, context=None): if not stage_id: - stage_id = self.stage_find(cr, uid, [], department_id, [('sequence', '=', '1')], context=context) + stage_id = self.stage_find(cr, uid, [], department_id, [('fold', '=', False)], context=context) return {'value': {'stage_id': stage_id}} def onchange_partner_id(self, cr, uid, ids, partner_id, context=None): @@ -331,7 +333,8 @@ class hr_applicant(osv.Model): 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 = {} val = msg.get('from').split('<')[0] defaults = { 'name': msg.get('subject') or _("No Subject"), @@ -344,38 +347,7 @@ class hr_applicant(osv.Model): if msg.get('priority'): defaults['priority'] = msg.get('priority') defaults.update(custom_values) - return super(hr_applicant,self).message_new(cr, uid, msg, custom_values=defaults, context=context) - - def message_update(self, cr, uid, ids, msg, update_vals=None, context=None): - """ Override mail_thread message_update that is called by the mailgateway - through message_process. - This method updates the document according to the email. - """ - if isinstance(ids, (str, int, long)): - ids = [ids] - if update_vals is None: - update_vals = {} - - update_vals.update({ - 'email_from': msg.get('from'), - 'email_cc': msg.get('cc'), - }) - if msg.get('priority'): - update_vals['priority'] = msg.get('priority') - - maps = { - 'cost': 'planned_cost', - 'revenue': 'planned_revenue', - 'probability': 'probability', - } - for line in msg.get('body', '').split('\n'): - line = line.strip() - res = tools.command_re.match(line) - if res and maps.get(res.group(1).lower(), False): - key = maps.get(res.group(1).lower()) - update_vals[key] = res.group(2).lower() - - return super(hr_applicant, self).message_update(cr, uid, ids, msg, update_vals=update_vals, context=context) + return super(hr_applicant, self).message_new(cr, uid, msg, custom_values=defaults, context=context) def create(self, cr, uid, vals, context=None): if context is None: diff --git a/addons/hr_recruitment/hr_recruitment_data.xml b/addons/hr_recruitment/hr_recruitment_data.xml index 854dea0548f..219f36405b5 100644 --- a/addons/hr_recruitment/hr_recruitment_data.xml +++ b/addons/hr_recruitment/hr_recruitment_data.xml @@ -71,12 +71,14 @@ Contract Signed 5 + Refused 6 + Job Survey 20 diff --git a/addons/hr_recruitment/hr_recruitment_view.xml b/addons/hr_recruitment/hr_recruitment_view.xml index 3bf09d21a17..7553a24ff3c 100644 --- a/addons/hr_recruitment/hr_recruitment_view.xml +++ b/addons/hr_recruitment/hr_recruitment_view.xml @@ -342,6 +342,7 @@ + diff --git a/addons/hr_recruitment/report/hr_recruitment_report.py b/addons/hr_recruitment/report/hr_recruitment_report.py index 0292875a2c7..908b701d928 100644 --- a/addons/hr_recruitment/report/hr_recruitment_report.py +++ b/addons/hr_recruitment/report/hr_recruitment_report.py @@ -90,7 +90,7 @@ class hr_recruitment_report(osv.Model): (sum(salary_proposed)/count(*)) as salary_prop_avg, sum(salary_expected) as salary_exp, (sum(salary_expected)/count(*)) as salary_exp_avg, - extract('epoch' from (s.date_closed-s.create_date))/(3600*24) as delay_close, + extract('epoch' from (s.write_date-s.create_date))/(3600*24) as delay_close, count(*) as nbr from hr_applicant s group by @@ -101,6 +101,7 @@ class hr_recruitment_report(osv.Model): date_trunc('day',s.date_closed), s.date_open, s.create_date, + s.write_date, s.date_closed, s.date_last_stage_update, s.partner_id, diff --git a/addons/hr_timesheet/hr_timesheet_view.xml b/addons/hr_timesheet/hr_timesheet_view.xml index 3d9e39e986f..0cd8a880a68 100644 --- a/addons/hr_timesheet/hr_timesheet_view.xml +++ b/addons/hr_timesheet/hr_timesheet_view.xml @@ -81,7 +81,8 @@ - {'search_default_account_id': [active_id], 'default_account_id': active_id, 'search_default_group_date': 1, 'search_default_group_journal': 1} + {'search_default_group_date': 1, 'search_default_group_journal': 1} + [('account_id','child_of', active_id)] Costs & Revenues account.analytic.line account.analytic.account diff --git a/addons/hr_timesheet_sheet/report/timesheet_report.py b/addons/hr_timesheet_sheet/report/timesheet_report.py index 16889253a68..6d284ac08c8 100644 --- a/addons/hr_timesheet_sheet/report/timesheet_report.py +++ b/addons/hr_timesheet_sheet/report/timesheet_report.py @@ -95,7 +95,7 @@ class timesheet_report(osv.osv): htss.state from account_analytic_line as aal left join hr_analytic_timesheet as hat ON (hat.line_id=aal.id) - left join hr_timesheet_sheet_sheet as htss ON (hat.line_id=htss.id) + left join hr_timesheet_sheet_sheet as htss ON (hat.sheet_id=htss.id) group by aal.account_id, aal.date, diff --git a/addons/im/static/src/js/im.js b/addons/im/static/src/js/im.js index 6fbf452f974..f4b74adebf6 100644 --- a/addons/im/static/src/js/im.js +++ b/addons/im/static/src/js/im.js @@ -62,16 +62,12 @@ this.user_search_dm = new instance.web.DropMisordered(); }, start: function() { + var self = this; this.$el.css("right", -this.$el.outerWidth()); $(window).scroll(_.bind(this.calc_box, this)); $(window).resize(_.bind(this.calc_box, this)); this.calc_box(); - this.on("change:current_search", this, this.search_changed); - this.search_changed(); - - var self = this; - return this.c_manager.start_polling().then(function() { self.c_manager.on("new_conversation", self, function(conv) { conv.$el.droppable({ @@ -80,6 +76,7 @@ } }); }); + self.search_changed(); }); }, calc_box: function() { @@ -174,4 +171,4 @@ openerp.webclient.to_kitten(); }; -})(); \ No newline at end of file +})(); diff --git a/addons/l10n_be/__openerp__.py b/addons/l10n_be/__openerp__.py index cd822045e64..8e607ba3120 100644 --- a/addons/l10n_be/__openerp__.py +++ b/addons/l10n_be/__openerp__.py @@ -65,6 +65,7 @@ Wizards provided by this module: 'account_pcmn_belgium.xml', 'account_tax_code_template.xml', 'account_chart_template.xml', + 'account_chart_template.yml', 'account_tax_template.xml', 'wizard/l10n_be_account_vat_declaration_view.xml', 'wizard/l10n_be_vat_intra_view.xml', diff --git a/addons/l10n_be/account_chart_template.xml b/addons/l10n_be/account_chart_template.xml index 48e506ded79..1de39382624 100644 --- a/addons/l10n_be/account_chart_template.xml +++ b/addons/l10n_be/account_chart_template.xml @@ -12,7 +12,6 @@ - diff --git a/addons/l10n_be/account_chart_template.yml b/addons/l10n_be/account_chart_template.yml new file mode 100644 index 00000000000..0028576f964 --- /dev/null +++ b/addons/l10n_be/account_chart_template.yml @@ -0,0 +1,4 @@ +- + !python {model: account.chart.template}: | + if 'spoken_languages' in self._all_columns: + self.write(cr, uid, [ref('l10nbe_chart_template')], {'spoken_languages': 'nl_BE'}) diff --git a/addons/l10n_be/wizard/l10n_be_account_vat_declaration.py b/addons/l10n_be/wizard/l10n_be_account_vat_declaration.py index 0576e1d5f8f..2ef1a81fef2 100644 --- a/addons/l10n_be/wizard/l10n_be_account_vat_declaration.py +++ b/addons/l10n_be/wizard/l10n_be_account_vat_declaration.py @@ -185,7 +185,7 @@ class l10n_be_vat_declaration(osv.osv_memory): for item in cases_list: grid_amount_data = { 'code': str(int(item['code'])), - 'amount': str(abs(item['sum_period'])), + 'amount': '%.2f' % abs(item['sum_period']), } data_of_file += '\n\t\t\t%(amount)s' % (grid_amount_data) diff --git a/addons/l10n_ch/sterchi_chart/account.xml b/addons/l10n_ch/sterchi_chart/account.xml index acee99cad4a..11198c43b9c 100644 --- a/addons/l10n_ch/sterchi_chart/account.xml +++ b/addons/l10n_ch/sterchi_chart/account.xml @@ -177,14 +177,12 @@ Bilan : Debiteurs receivable - unreconciled none Bilan : Fournisseurs payable - unreconciled none @@ -11809,7 +11807,6 @@ - diff --git a/addons/l10n_es/taxes_data_assoc.xml b/addons/l10n_es/taxes_data_assoc.xml index e2ebb47fb59..152b27363af 100644 --- a/addons/l10n_es/taxes_data_assoc.xml +++ b/addons/l10n_es/taxes_data_assoc.xml @@ -44,7 +44,6 @@ Base adquisiciones exentas - -- 1.0 @@ -53,7 +52,6 @@ Base ventas exentas -- - 1.0 diff --git a/addons/l10n_fr/fr_tax.xml b/addons/l10n_fr/fr_tax.xml index 7c75136bdd3..8633cca7141 100644 --- a/addons/l10n_fr/fr_tax.xml +++ b/addons/l10n_fr/fr_tax.xml @@ -219,8 +219,8 @@ - - + + diff --git a/addons/l10n_in_hr_payroll/data/hr.salary.rule.csv b/addons/l10n_in_hr_payroll/data/hr.salary.rule.csv index 2fe1ba739f4..043ba8f0522 100644 --- a/addons/l10n_in_hr_payroll/data/hr.salary.rule.csv +++ b/addons/l10n_in_hr_payroll/data/hr.salary.rule.csv @@ -1,5 +1,5 @@ -"id","amount_select","condition_range_min","condition_range_max","amount_percentage","amount_fix","name","category_id","sequence","code","parent_rule_id/id","condition_select","condition_range","amount_percentage_base" -1,"fix",1,1,,100,"Education Allowance For One Child","Allowance",23,"CHEAONE","hr_payroll_rule_child1","range","employee.children", -2,"fix",2,10,,200,"Education Allowance For Two Child","Allowance",24,"CHEATWO","hr_payroll_rule_child1","range","employee.children", -3,"fix",1,1,,300,"Child Hostel Allowance For One Child","Allowance",26,"CHOONE","hr_payroll_rule_child2","range","employee.children", -4,"fix",2,10,,600,"Child Hostel Allowance For Two Child","Allowance",27,"CHOTWO","hr_payroll_rule_child2","range","employee.children", +"id","amount_select","condition_range_min","condition_range_max","amount_percentage","amount_fix","name","category_id/id","sequence","code","parent_rule_id/id","condition_select","condition_range","amount_percentage_base" +1,"fix",1,1,,100,"Education Allowance For One Child",hr_payroll.ALW,23,"CHEAONE","hr_payroll_rule_child1","range","employee.children", +2,"fix",2,10,,200,"Education Allowance For Two Child",hr_payroll.ALW,24,"CHEATWO","hr_payroll_rule_child1","range","employee.children", +3,"fix",1,1,,300,"Child Hostel Allowance For One Child",hr_payroll.ALW,26,"CHOONE","hr_payroll_rule_child2","range","employee.children", +4,"fix",2,10,,600,"Child Hostel Allowance For Two Child",hr_payroll.ALW,27,"CHOTWO","hr_payroll_rule_child2","range","employee.children", diff --git a/addons/l10n_multilang/l10n_multilang.py b/addons/l10n_multilang/l10n_multilang.py index b7564a9960a..937ba332694 100644 --- a/addons/l10n_multilang/l10n_multilang.py +++ b/addons/l10n_multilang/l10n_multilang.py @@ -33,6 +33,7 @@ class wizard_multi_charts_accounts(osv.osv_memory): """ _inherit = 'wizard.multi.charts.accounts' + # FIXME: in trunk, drop the force_write param entirely def process_translations(self, cr, uid, langs, in_obj, in_field, in_ids, out_obj, out_ids, force_write=False, context=None): """ This method copies translations values of templates into new Accounts/Taxes/Journals for languages selected @@ -45,8 +46,7 @@ class wizard_multi_charts_accounts(osv.osv_memory): :param in_ids: List of ids of source object :param out_obj: Destination object for which translation is to be copied :param out_ids: List of ids of destination object - :param force_write: boolean that depicts if we need to create a translation OR simply replace the actual value - with the translation in the uid's language by doing a write (in case it's TRUE) + :param force_write: Deprecated as of 7.0, do not use :param context: usual context information. May contain the key 'lang', which is the language of the user running the wizard, that will be used if force_write is True @@ -65,26 +65,25 @@ class wizard_multi_charts_accounts(osv.osv_memory): for j in range(len(in_ids)): in_id = in_ids[j] if value[in_id]: - if not force_write: - #copy Translation from Source to Destination object - xlat_obj.create(cr, uid, { - 'name': out_obj._name + ',' + in_field, - 'type': 'model', - 'res_id': out_ids[j], - 'lang': lang, - 'src': src[in_id], - 'value': value[in_id], + #copy Translation from Source to Destination object + xlat_obj.create(cr, uid, { + 'name': out_obj._name + ',' + in_field, + 'type': 'model', + 'res_id': out_ids[j], + 'lang': lang, + 'src': src[in_id], + 'value': value[in_id], }) - else: - #replace the value in the destination object only if it's the user lang - if context.get('lang') == lang: - self.pool[out_obj._name].write(cr, uid, out_ids[j], {in_field: value[in_id]}) else: _logger.info('Language: %s. Translation from template: there is no translation available for %s!' %(lang, src[in_id]))#out_obj._name)) return True def execute(self, cr, uid, ids, context=None): - res = super(wizard_multi_charts_accounts, self).execute(cr, uid, ids, context=context) + if not context: + context = {} + # remove the lang to get the untranslated value + ctx = dict(context, lang=None) + res = super(wizard_multi_charts_accounts, self).execute(cr, uid, ids, context=ctx) obj_multi = self.browse(cr, uid, ids[0], context=context) company_id = obj_multi.company_id.id @@ -125,7 +124,7 @@ class wizard_multi_charts_accounts(osv.osv_memory): acc_root_id = obj_acc.search(cr, uid, [('company_id', '=', company_id), ('parent_id', '=', None)])[0] in_ids = obj_acc_template.search(cr, uid, [('id', 'child_of', [acc_template_root_id])], order='id')[1:] out_ids = obj_acc.search(cr, uid, [('id', 'child_of', [acc_root_id])], order='id')[1:] - return self.process_translations(cr, uid, langs, obj_acc_template, field, in_ids, obj_acc, out_ids, force_write=True, context=context) + return self.process_translations(cr, uid, langs, obj_acc_template, field, in_ids, obj_acc, out_ids, context=context) def _process_tax_codes_translations(self, cr, uid, obj_multi, company_id, langs, field, context=None): obj_tax_code_template = self.pool.get('account.tax.code.template') @@ -134,21 +133,21 @@ class wizard_multi_charts_accounts(osv.osv_memory): tax_code_root_id = obj_tax_code.search(cr, uid, [('company_id', '=', company_id), ('parent_id', '=', None)])[0] in_ids = obj_tax_code_template.search(cr, uid, [('id', 'child_of', [tax_code_template_root_id])], order='id')[1:] out_ids = obj_tax_code.search(cr, uid, [('id', 'child_of', [tax_code_root_id])], order='id')[1:] - return self.process_translations(cr, uid, langs, obj_tax_code_template, field, in_ids, obj_tax_code, out_ids, force_write=False, context=context) + return self.process_translations(cr, uid, langs, obj_tax_code_template, field, in_ids, obj_tax_code, out_ids, context=context) def _process_taxes_translations(self, cr, uid, obj_multi, company_id, langs, field, context=None): obj_tax_template = self.pool.get('account.tax.template') obj_tax = self.pool.get('account.tax') in_ids = sorted([x.id for x in obj_multi.chart_template_id.tax_template_ids]) out_ids = obj_tax.search(cr, uid, [('company_id', '=', company_id)], order='id') - return self.process_translations(cr, uid, langs, obj_tax_template, field, in_ids, obj_tax, out_ids, force_write=False, context=context) + return self.process_translations(cr, uid, langs, obj_tax_template, field, in_ids, obj_tax, out_ids, context=context) def _process_fiscal_pos_translations(self, cr, uid, obj_multi, company_id, langs, field, context=None): obj_fiscal_position_template = self.pool.get('account.fiscal.position.template') obj_fiscal_position = self.pool.get('account.fiscal.position') in_ids = obj_fiscal_position_template.search(cr, uid, [('chart_template_id', '=', obj_multi.chart_template_id.id)], order='id') out_ids = obj_fiscal_position.search(cr, uid, [('company_id', '=', company_id)], order='id') - return self.process_translations(cr, uid, langs, obj_fiscal_position_template, field, in_ids, obj_fiscal_position, out_ids, force_write=False, context=context) + return self.process_translations(cr, uid, langs, obj_fiscal_position_template, field, in_ids, obj_fiscal_position, out_ids, context=context) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/l10n_mx/data/account_chart.xml b/addons/l10n_mx/data/account_chart.xml index daa564fd10e..c28d8da8a44 100644 --- a/addons/l10n_mx/data/account_chart.xml +++ b/addons/l10n_mx/data/account_chart.xml @@ -3693,8 +3693,6 @@ Cuentas del plan - - diff --git a/addons/l10n_pt/account_chart_template.xml b/addons/l10n_pt/account_chart_template.xml index 49adfa393ef..08984b8d2af 100644 --- a/addons/l10n_pt/account_chart_template.xml +++ b/addons/l10n_pt/account_chart_template.xml @@ -15,7 +15,6 @@ - diff --git a/addons/l10n_us/account_chart_template.xml b/addons/l10n_us/account_chart_template.xml index 704afbad152..0adeddc5856 100644 --- a/addons/l10n_us/account_chart_template.xml +++ b/addons/l10n_us/account_chart_template.xml @@ -6,7 +6,6 @@ Basic Chart of Account - diff --git a/addons/l10n_ve/data/account_tax.xml b/addons/l10n_ve/data/account_tax.xml index 7acbda3e2af..8fb1aa4b819 100644 --- a/addons/l10n_ve/data/account_tax.xml +++ b/addons/l10n_ve/data/account_tax.xml @@ -7,8 +7,8 @@ 0.00000 percent all - - + + @@ -21,8 +21,8 @@ 0.120000 percent sale - - + + @@ -34,8 +34,8 @@ 0.080000 percent sale - - + + @@ -47,8 +47,8 @@ 0.220000 percent sale - - + + @@ -60,8 +60,8 @@ 0.120000 percent purchase - - + + @@ -73,8 +73,8 @@ 0.080000 percent purchase - - + + @@ -86,8 +86,8 @@ 0.220000 percent purchase - - + + diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py index 72ccdc83bb6..de8e4c68fda 100644 --- a/addons/mail/mail_message.py +++ b/addons/mail/mail_message.py @@ -698,8 +698,9 @@ class mail_message(osv.Model): """ model_record_ids = {} for id in msg_ids: - if msg_val[id]['model'] and msg_val[id]['res_id']: - model_record_ids.setdefault(msg_val[id]['model'], dict()).setdefault(msg_val[id]['res_id'], set()).add(msg_val[id]['res_id']) + vals = msg_val.get(id, {}) + if vals.get('model') and vals.get('res_id'): + model_record_ids.setdefault(vals['model'], set()).add(vals['res_id']) return model_record_ids if uid == SUPERUSER_ID: @@ -711,7 +712,7 @@ class mail_message(osv.Model): partner_id = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=None).partner_id.id # Read mail_message.ids to have their values - message_values = dict.fromkeys(ids) + message_values = dict.fromkeys(ids, {}) cr.execute('SELECT DISTINCT id, model, res_id, author_id, parent_id FROM "%s" WHERE id = ANY (%%s)' % self._table, (ids,)) for id, rmod, rid, author_id, parent_id in cr.fetchall(): message_values[id] = {'model': rmod, 'res_id': rid, 'author_id': author_id, 'parent_id': parent_id} @@ -745,10 +746,10 @@ class mail_message(osv.Model): ], context=context) notified_ids = [notification.message_id.id for notification in not_obj.browse(cr, SUPERUSER_ID, not_ids, context=context)] elif operation == 'create': - for doc_model, doc_dict in model_record_ids.items(): + for doc_model, doc_ids in model_record_ids.items(): fol_ids = fol_obj.search(cr, SUPERUSER_ID, [ ('res_model', '=', doc_model), - ('res_id', 'in', list(doc_dict.keys())), + ('res_id', 'in', list(doc_ids)), ('partner_id', '=', partner_id), ], context=context) fol_mids = [follower.res_id for follower in fol_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context)] @@ -759,9 +760,9 @@ class mail_message(osv.Model): other_ids = other_ids.difference(set(notified_ids)) model_record_ids = _generate_model_record_ids(message_values, other_ids) document_related_ids = [] - for model, doc_dict in model_record_ids.items(): + for model, doc_ids in model_record_ids.items(): model_obj = self.pool[model] - mids = model_obj.exists(cr, uid, doc_dict.keys()) + mids = model_obj.exists(cr, uid, list(doc_ids)) if hasattr(model_obj, 'check_mail_message_access'): model_obj.check_mail_message_access(cr, uid, mids, operation, context=context) else: diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index a5d6d9a3614..dfee2a04d4d 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -38,6 +38,7 @@ from openerp import tools from openerp import SUPERUSER_ID from openerp.addons.mail.mail_message import decode from openerp.osv import fields, osv, orm +from openerp.osv.orm import browse_record, browse_null from openerp.tools.safe_eval import safe_eval as eval from openerp.tools.translate import _ @@ -355,11 +356,11 @@ class mail_thread(osv.AbstractModel): if not context.get('mail_create_nosubscribe'): self.message_subscribe_users(cr, uid, [thread_id], [uid], context=context) # auto_subscribe: take values and defaults into account - create_values = set(values.keys()) + create_values = dict(values) for key, val in context.iteritems(): if key.startswith('default_'): - create_values.add(key[8:]) - self.message_auto_subscribe(cr, uid, [thread_id], list(create_values), context=context) + create_values[key[8:]] = val + self.message_auto_subscribe(cr, uid, [thread_id], create_values.keys(), context=context, values=create_values) # track values tracked_fields = self._get_tracked_fields(cr, uid, values.keys(), context=context) @@ -380,7 +381,7 @@ class mail_thread(osv.AbstractModel): # Perform write, update followers result = super(mail_thread, self).write(cr, uid, ids, values, context=context) - self.message_auto_subscribe(cr, uid, ids, values.keys(), context=context) + self.message_auto_subscribe(cr, uid, ids, values.keys(), context=context, values=values) # Perform the tracking if tracked_fields: @@ -604,7 +605,8 @@ class mail_thread(osv.AbstractModel): return action if msg_id and not (model and res_id): msg = self.pool.get('mail.message').browse(cr, uid, msg_id, context=context) - model, res_id = msg.model, msg.res_id + if msg.exists(): + model, res_id = msg.model, msg.res_id # if model + res_id found: try to redirect to the document or fallback on the Inbox if model and res_id: @@ -1565,75 +1567,100 @@ class mail_thread(osv.AbstractModel): user_field_lst.append(name) return user_field_lst - def message_auto_subscribe(self, cr, uid, ids, updated_fields, context=None): - """ - 1. fetch project subtype related to task (parent_id.res_model = 'project.task') - 2. for each project subtype: subscribe the follower to the task + def message_auto_subscribe(self, cr, uid, ids, updated_fields, context=None, values=None): + """ Handle auto subscription. Two methods for auto subscription exist: + + - tracked res.users relational fields, such as user_id fields. Those fields + must be relation fields toward a res.users record, and must have the + track_visilibity attribute set. + - using subtypes parent relationship: check if the current model being + modified has an header record (such as a project for tasks) whose followers + can be added as followers of the current records. Example of structure + with project and task: + + - st_project_1.parent_id = st_task_1 + - st_project_1.res_model = 'project.project' + - st_project_1.relation_field = 'project_id' + - st_task_1.model = 'project.task' + + :param list updated_fields: list of updated fields to track + :param dict values: updated values; if None, the first record will be browsed + to get the values. Added after releasing 7.0, therefore + not merged with updated_fields argumment. """ subtype_obj = self.pool.get('mail.message.subtype') follower_obj = self.pool.get('mail.followers') + new_followers = dict() - # fetch auto_follow_fields + # fetch auto_follow_fields: res.users relation fields whose changes are tracked for subscription user_field_lst = self._message_get_auto_subscribe_fields(cr, uid, updated_fields, context=context) - # fetch related record subtypes - related_subtype_ids = subtype_obj.search(cr, uid, ['|', ('res_model', '=', False), ('parent_id.res_model', '=', self._name)], context=context) - subtypes = subtype_obj.browse(cr, uid, related_subtype_ids, context=context) - default_subtypes = [subtype for subtype in subtypes if subtype.res_model == False] - related_subtypes = [subtype for subtype in subtypes if subtype.res_model != False] - relation_fields = set([subtype.relation_field for subtype in subtypes if subtype.relation_field != False]) - if (not related_subtypes or not any(relation in updated_fields for relation in relation_fields)) and not user_field_lst: + # fetch header subtypes + header_subtype_ids = subtype_obj.search(cr, uid, ['|', ('res_model', '=', False), ('parent_id.res_model', '=', self._name)], context=context) + subtypes = subtype_obj.browse(cr, uid, header_subtype_ids, context=context) + + # if no change in tracked field or no change in tracked relational field: quit + relation_fields = set([subtype.relation_field for subtype in subtypes if subtype.relation_field is not False]) + if not any(relation in updated_fields for relation in relation_fields) and not user_field_lst: return True - for record in self.browse(cr, uid, ids, context=context): - new_followers = dict() - parent_res_id = False - parent_model = False - for subtype in related_subtypes: - if not subtype.relation_field or not subtype.parent_id: - continue - if not subtype.relation_field in self._columns or not getattr(record, subtype.relation_field, False): - continue - parent_res_id = getattr(record, subtype.relation_field).id - parent_model = subtype.res_model - follower_ids = follower_obj.search(cr, SUPERUSER_ID, [ - ('res_model', '=', parent_model), - ('res_id', '=', parent_res_id), - ('subtype_ids', 'in', [subtype.id]) - ], context=context) - for follower in follower_obj.browse(cr, SUPERUSER_ID, follower_ids, context=context): - new_followers.setdefault(follower.partner_id.id, set()).add(subtype.parent_id.id) + # legacy behavior: if values is not given, compute the values by browsing + # @TDENOTE: remove me in 8.0 + if values is None: + record = self.browse(cr, uid, ids[0], context=context) + for updated_field in updated_fields: + field_value = getattr(record, updated_field) + if isinstance(field_value, browse_record): + field_value = field_value.id + elif isinstance(field_value, browse_null): + field_value = False + values[updated_field] = field_value - if parent_res_id and parent_model: - for subtype in default_subtypes: - follower_ids = follower_obj.search(cr, SUPERUSER_ID, [ - ('res_model', '=', parent_model), - ('res_id', '=', parent_res_id), - ('subtype_ids', 'in', [subtype.id]) - ], context=context) - for follower in follower_obj.browse(cr, SUPERUSER_ID, follower_ids, context=context): - new_followers.setdefault(follower.partner_id.id, set()).add(subtype.id) + # find followers of headers, update structure for new followers + headers = set() + for subtype in subtypes: + if subtype.relation_field and values.get(subtype.relation_field): + headers.add((subtype.res_model, values.get(subtype.relation_field))) + if headers: + header_domain = ['|'] * (len(headers) - 1) + for header in headers: + header_domain += ['&', ('res_model', '=', header[0]), ('res_id', '=', header[1])] + header_follower_ids = follower_obj.search( + cr, SUPERUSER_ID, + header_domain, + context=context + ) + for header_follower in follower_obj.browse(cr, SUPERUSER_ID, header_follower_ids, context=context): + for subtype in header_follower.subtype_ids: + if subtype.parent_id and subtype.parent_id.res_model == self._name: + new_followers.setdefault(header_follower.partner_id.id, set()).add(subtype.parent_id.id) + elif subtype.res_model is False: + new_followers.setdefault(header_follower.partner_id.id, set()).add(subtype.id) - # 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)] - 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) + # add followers coming from res.users relational fields that are tracked + user_ids = [values[name] for name in user_field_lst if values.get(name)] + user_pids = [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_pids: + 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) + for pid, subtypes in new_followers.items(): + subtypes = list(subtypes) if subtypes is not None else None + self.message_subscribe(cr, uid, ids, [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] + # find first email message, set it as unread for auto_subscribe fields for them to have a notification + if user_pids: + for record_id in ids: + message_obj = self.pool.get('mail.message') + msg_ids = message_obj.search(cr, SUPERUSER_ID, [ + ('model', '=', self._name), + ('res_id', '=', record_id), + ('type', '=', 'email')], limit=1, context=context) + if not msg_ids: + msg_ids = message_obj.search(cr, SUPERUSER_ID, [ + ('model', '=', self._name), + ('res_id', '=', record_id)], limit=1, context=context) if msg_ids: - self.pool.get('mail.notification')._notify(cr, uid, msg_ids[0], partners_to_notify=user_id_partner_ids, context=context) + self.pool.get('mail.notification')._notify(cr, uid, msg_ids[0], partners_to_notify=user_pids, context=context) return True diff --git a/addons/mail/tests/test_mail_features.py b/addons/mail/tests/test_mail_features.py index 62c76db5a40..c7f1056c59a 100644 --- a/addons/mail/tests/test_mail_features.py +++ b/addons/mail/tests/test_mail_features.py @@ -294,9 +294,9 @@ class test_mail(TestMail): 'notification email: link should contain the user login') self.assertIn('message_id=%s' % mail.mail_message_id.id, url, 'notification email: link based on message should contain the mail_message id') - self.assertNotIn('model', url, + self.assertNotIn('model=mail.group', url, 'notification email: link based on message should not contain model') - self.assertNotIn('res_id', url, + self.assertNotIn('res_id=%s' % group_pigs.id, url, 'notification email: link based on message should not contain res_id') @mute_logger('openerp.addons.mail.mail_thread', 'openerp.osv.orm') diff --git a/addons/marketing_campaign/marketing_campaign_demo.xml b/addons/marketing_campaign/marketing_campaign_demo.xml index 89a299e3a85..a7b1a8bce0c 100644 --- a/addons/marketing_campaign/marketing_campaign_demo.xml +++ b/addons/marketing_campaign/marketing_campaign_demo.xml @@ -5,7 +5,7 @@ welcome new partner info@openerp.com Welcome to the OpenERP Partner Channel! - ${object.email or ''} + ${(object.email or '')|safe} Hello, you will receive your welcome pack via email shortly. @@ -13,7 +13,7 @@ congrats silver partner info@openerp.com Congratulations! You are now a Silver Partner! - ${object.email or ''} + ${(object.email or '')|safe} Hi, we are delighted to welcome you among our Silver Partners as of today! @@ -22,7 +22,7 @@ congrats gold partner info@openerp.com Congratulations! You are now one of our Gold Partners! - ${object.email or ''} + ${(object.email or '')|safe} Hi, we are delighted to let you know that you have entered the select circle of our Gold Partners diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py index 1d0a0c98d48..4ef2a6a4f4c 100644 --- a/addons/mrp/mrp.py +++ b/addons/mrp/mrp.py @@ -965,7 +965,10 @@ class mrp_production(osv.osv): partner_id = routing_loc.partner_id and routing_loc.partner_id.id or False # Take next Sequence number of shipment base on type - pick_name = ir_sequence.get(cr, uid, 'stock.picking.' + pick_type) + if pick_type!='internal': + pick_name = ir_sequence.get(cr, uid, 'stock.picking.' + pick_type) + else: + pick_name = ir_sequence.get(cr, uid, 'stock.picking') picking_id = stock_picking.create(cr, uid, { 'name': pick_name, diff --git a/addons/mrp_byproduct/mrp_byproduct.py b/addons/mrp_byproduct/mrp_byproduct.py index 2425c2d8fea..38058abf071 100644 --- a/addons/mrp_byproduct/mrp_byproduct.py +++ b/addons/mrp_byproduct/mrp_byproduct.py @@ -80,11 +80,11 @@ class mrp_production(osv.osv): _inherit= 'mrp.production' - def action_confirm(self, cr, uid, ids): + def action_confirm(self, cr, uid, ids, context=None): """ Confirms production order and calculates quantity based on subproduct_type. @return: Newly generated picking Id. """ - picking_id = super(mrp_production,self).action_confirm(cr, uid, ids) + picking_id = super(mrp_production,self).action_confirm(cr, uid, ids, context=context) product_uom_obj = self.pool.get('product.uom') for production in self.browse(cr, uid, ids): source = production.product_id.property_stock_production.id diff --git a/addons/pad/pad.py b/addons/pad/pad.py index 4cd9e91b76e..474321edb1d 100644 --- a/addons/pad/pad.py +++ b/addons/pad/pad.py @@ -39,7 +39,11 @@ class pad_common(osv.osv_memory): #if create with content if "field_name" in context and "model" in context and "object_id" in context: myPad = EtherpadLiteClient( pad["key"], pad["server"]+'/api') - myPad.createPad(path) + try: + myPad.createPad(path) + except urllib2.URLError: + raise osv.except_osv(_("Error"), _("Pad creation failed, \ + either there is a problem with your pad server URL or with your connection.")) #get attr on the field model model = self.pool[context["model"]] diff --git a/addons/pad/pad_demo.xml b/addons/pad/pad_demo.xml index 99c4b38481c..5fd76f65e21 100644 --- a/addons/pad/pad_demo.xml +++ b/addons/pad/pad_demo.xml @@ -2,7 +2,7 @@ - pad.openerp.com + https://pad.openerp.com diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index d32b8e91d3a..a7bc2f5f59e 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -694,7 +694,7 @@ class pos_order(osv.osv): def create_picking(self, cr, uid, ids, context=None): """Create a picking for each order and validate it.""" - picking_obj = self.pool.get('stock.picking') + picking_obj = self.pool.get('stock.picking.out') partner_obj = self.pool.get('res.partner') move_obj = self.pool.get('stock.move') diff --git a/addons/point_of_sale/point_of_sale_view.xml b/addons/point_of_sale/point_of_sale_view.xml index 7202b57a1eb..0f26a7c85b9 100644 --- a/addons/point_of_sale/point_of_sale_view.xml +++ b/addons/point_of_sale/point_of_sale_view.xml @@ -56,7 +56,7 @@

    diff --git a/addons/point_of_sale/static/src/img/open-cashbox.png.moved b/addons/point_of_sale/static/src/img/open-cashbox.png.moved new file mode 100644 index 00000000000..1e07b0a9282 Binary files /dev/null and b/addons/point_of_sale/static/src/img/open-cashbox.png.moved differ diff --git a/addons/portal/mail_message.py b/addons/portal/mail_message.py index 9699b3a9ef6..4429df3be8b 100644 --- a/addons/portal/mail_message.py +++ b/addons/portal/mail_message.py @@ -19,6 +19,7 @@ # ############################################################################## +from openerp import SUPERUSER_ID from openerp.osv import osv, orm from openerp.tools.translate import _ @@ -32,6 +33,9 @@ class mail_message(osv.Model): """ Override that adds specific access rights of mail.message, to remove all internal notes if uid is a non-employee """ + if uid == SUPERUSER_ID: + return super(mail_message, self)._search(cr, uid, args, offset=offset, limit=limit, order=order, + context=context, count=False, access_rights_uid=access_rights_uid) group_ids = self.pool.get('res.users').browse(cr, uid, uid, context=context).groups_id group_user_id = self.pool.get("ir.model.data").get_object_reference(cr, uid, 'base', 'group_user')[1] if group_user_id not in [group.id for group in group_ids]: @@ -45,6 +49,8 @@ class mail_message(osv.Model): - read: - raise if the type is comment and subtype NULL (internal note) """ + if uid == SUPERUSER_ID: + return super(mail_message, self).check_access_rule(cr, uid, ids=ids, operation=operation, context=context) group_ids = self.pool.get('res.users').browse(cr, uid, uid, context=context).groups_id group_user_id = self.pool.get("ir.model.data").get_object_reference(cr, uid, 'base', 'group_user')[1] if group_user_id not in [group.id for group in group_ids]: diff --git a/addons/portal/tests/test_portal.py b/addons/portal/tests/test_portal.py index f5eeba0aa62..12c64d7588c 100644 --- a/addons/portal/tests/test_portal.py +++ b/addons/portal/tests/test_portal.py @@ -73,6 +73,9 @@ class test_portal(TestMail): for message in chell_pigs.message_ids: trigger_read = message.subject for partner in chell_pigs.message_follower_ids: + if partner.id == self.partner_chell_id: + # Chell can read her own partner record + continue with self.assertRaises(except_orm): trigger_read = partner.name diff --git a/addons/portal_claim/security/portal_security.xml b/addons/portal_claim/security/portal_security.xml index af5261c408b..1f96da4ef9e 100644 --- a/addons/portal_claim/security/portal_security.xml +++ b/addons/portal_claim/security/portal_security.xml @@ -1,17 +1,15 @@ - - + Portal Personal Claims - [('message_follower_ids','in', [user.partner_id.id])] + ['|', ('message_follower_ids','in', [user.partner_id.id]), ('partner_id','=', user.partner_id.id)] - - + + - + - diff --git a/addons/portal_hr_employees/hr_employee.py b/addons/portal_hr_employees/hr_employee.py index 52d01170a2a..b6d3ed1a7b5 100644 --- a/addons/portal_hr_employees/hr_employee.py +++ b/addons/portal_hr_employees/hr_employee.py @@ -24,7 +24,6 @@ from openerp.osv import fields, osv class crm_contact_us(osv.TransientModel): """ Add employees list to the portal's contact page """ _inherit = 'portal_crm.crm_contact_us' - _description = 'Contact form for the portal' _columns = { 'employee_ids' : fields.many2many('hr.employee', string='Employees', readonly=True), } @@ -40,7 +39,6 @@ class crm_contact_us(osv.TransientModel): } class hr_employee(osv.osv): - _description = 'Portal employee' _inherit = 'hr.employee' """ diff --git a/addons/portal_sale/portal_sale_data.xml b/addons/portal_sale/portal_sale_data.xml index 2cd7ca8b501..750ae12995f 100644 --- a/addons/portal_sale/portal_sale_data.xml +++ b/addons/portal_sale/portal_sale_data.xml @@ -6,7 +6,7 @@ Sales Order - Send by Email (Portal) - ${object.user_id.email or ''} + ${(object.user_id.email or '')|safe} ${object.company_id.name} ${object.state in ('draft', 'sent') and 'Quotation' or 'Order'} (Ref ${object.name or 'n/a' }) ${object.partner_invoice_id.id} @@ -95,7 +95,7 @@ Invoice - Send by Email (Portal) - ${object.user_id.email or object.company_id.email or 'noreply@localhost'} + ${(object.user_id.email or object.company_id.email or 'noreply@localhost')|safe} ${object.company_id.name} Invoice (Ref ${object.number or 'n/a' }) ${object.partner_id.id} diff --git a/addons/procurement/procurement.py b/addons/procurement/procurement.py index 032952aa511..c2878df69cb 100644 --- a/addons/procurement/procurement.py +++ b/addons/procurement/procurement.py @@ -181,10 +181,6 @@ class procurement_order(osv.osv): """ return all(procurement.move_id.state == 'cancel' for procurement in self.browse(cr, uid, ids, context=context)) - #This Function is create to avoid a server side Error Like 'ERROR:tests.mrp:name 'check_move' is not defined' - def check_move(self, cr, uid, ids, context=None): - pass - def check_move_done(self, cr, uid, ids, context=None): """ Checks if move is done or not. @return: True or False. @@ -297,6 +293,12 @@ class procurement_order(osv.osv): """ return False + def check_move(self, cr, uid, ids, context=None): + """ Check whether the given procurement can be satisfied by an internal move, + typically a pulled flow. By default, it's False. Overwritten by the `stock_location` module. + """ + return False + def check_conditions_confirm2wait(self, cr, uid, ids): """ condition on the transition to go from 'confirm' activity to 'confirm_wait' activity """ return not self.test_cancel(cr, uid, ids) diff --git a/addons/procurement/procurement_workflow.xml b/addons/procurement/procurement_workflow.xml index 76b54370ef2..d4068a6eada 100644 --- a/addons/procurement/procurement_workflow.xml +++ b/addons/procurement/procurement_workflow.xml @@ -147,13 +147,15 @@ - not check_produce() and not check_buy() + not check_produce() and not check_buy() and not check_move() diff --git a/addons/procurement/schedulers.py b/addons/procurement/schedulers.py index d82fa94c132..0828b4f10df 100644 --- a/addons/procurement/schedulers.py +++ b/addons/procurement/schedulers.py @@ -173,6 +173,7 @@ class procurement_order(osv.osv): 'company_id': orderpoint.company_id.id, 'product_uom': orderpoint.product_uom.id, 'location_id': orderpoint.location_id.id, + 'warehouse_id': orderpoint.warehouse_id.id, 'procure_method': 'make_to_order', 'origin': orderpoint.name} diff --git a/addons/product_visible_discount/product_visible_discount.py b/addons/product_visible_discount/product_visible_discount.py index a2f0a41847f..bd43028b36a 100644 --- a/addons/product_visible_discount/product_visible_discount.py +++ b/addons/product_visible_discount/product_visible_discount.py @@ -86,78 +86,13 @@ class sale_order_line(osv.osv): pricelists = pricelist_obj.read(cr,uid,[pricelist],['visible_discount']) new_list_price = get_real_price(list_price, product.id, qty, uom, pricelist) - if(len(pricelists)>0 and pricelists[0]['visible_discount'] and list_price[pricelist] != 0): + if len(pricelists)>0 and pricelists[0]['visible_discount'] and list_price[pricelist] != 0 and new_list_price != 0: discount = (new_list_price - price) / new_list_price * 100 - result['price_unit'] = new_list_price - result['discount'] = discount + if discount > 0: + result['price_unit'] = new_list_price + result['discount'] = discount + else: + result['discount'] = 0.0 else: result['discount'] = 0.0 return res - - -class account_invoice_line(osv.osv): - _inherit = "account.invoice.line" - - 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 = 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) - - def get_real_price(res_dict, product_id, qty, uom_id, pricelist): - item_obj = self.pool.get('product.pricelist.item') - price_type_obj = self.pool.get('product.price.type') - product_obj = self.pool.get('product.product') - field_name = 'list_price' - - if res_dict.get('item_id',False) and res_dict['item_id'].get(pricelist,False): - item = res_dict['item_id'].get(pricelist,False) - item_read = item_obj.read(cr, uid, [item], ['base']) - if item_read: - item_base = item_read[0]['base'] - if item_base > 0: - field_name = price_type_obj.browse(cr, uid, item_base).field - - product = product_obj.browse(cr, uid, product_id, context) - product_read = product_obj.read(cr, uid, product_id, [field_name], context=context) - - factor = 1.0 - if uom_id and uom_id != product.uom_id.id: - product_uom_obj = self.pool.get('product.uom') - uom_data = product_uom_obj.browse(cr, uid, product.uom_id.id) - factor = uom_data.factor - return product_read[field_name] * factor - - if product: - pricelist_obj = self.pool.get('product.pricelist') - partner_obj = self.pool.get('res.partner') - product = self.pool.get('product.product').browse(cr, uid, product, context=context) - result = res['value'] - pricelist = False - real_price = 0.00 - if type in ('in_invoice', 'in_refund'): - if not price_unit and partner_id: - pricelist =partner_obj.browse(cr, uid, partner_id).property_product_pricelist_purchase.id - if not pricelist: - raise osv.except_osv(_('No Purchase Pricelist Found!'),_("You must first define a pricelist on the supplier form!")) - price_unit_res = pricelist_obj.price_get(cr, uid, [pricelist], product.id, qty or 1.0, partner_id, {'uom': uom_id}) - price_unit = price_unit_res[pricelist] - real_price = get_real_price(price_unit_res, product.id, qty, uom_id, pricelist) - else: - if partner_id: - pricelist = partner_obj.browse(cr, uid, partner_id).property_product_pricelist.id - if not pricelist: - raise osv.except_osv(_('No Sale Pricelist Found!'),_("You must first define a pricelist on the customer form!")) - price_unit_res = pricelist_obj.price_get(cr, uid, [pricelist], product.id, qty or 1.0, partner_id, {'uom': uom_id}) - price_unit = price_unit_res[pricelist] - - real_price = get_real_price(price_unit_res, product.id, qty, uom_id, pricelist) - if pricelist: - pricelists=pricelist_obj.read(cr,uid,[pricelist],['visible_discount']) - if(len(pricelists)>0 and pricelists[0]['visible_discount'] and real_price != 0): - discount=(real_price-price_unit) / real_price * 100 - result['price_unit'] = real_price - result['discount'] = discount - else: - result['discount']=0.0 - return res - - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/project/project.py b/addons/project/project.py index 4e2b6c6b17b..ea9c4492fa4 100644 --- a/addons/project/project.py +++ b/addons/project/project.py @@ -41,14 +41,13 @@ class project_task_type(osv.osv): 'case_default': fields.boolean('Default for New Projects', help="If you check this field, this stage will be proposed by default on each new project. It will not assign this stage to existing projects."), 'project_ids': fields.many2many('project.project', 'project_task_type_rel', 'type_id', 'project_id', 'Projects'), - 'fold': fields.boolean('Folded by Default', - help="This stage is not visible, for example in status bar or kanban view, when there are no records in that stage to display."), + 'fold': fields.boolean('Folded in Kanban View', + help='This stage is folded in the kanban view when' + 'there are no records in that stage to display.'), } _defaults = { 'sequence': 1, - 'fold': False, - 'case_default': False, 'project_ids': lambda self, cr, uid, ctx=None: self.pool['project.task']._get_default_project_id(cr, uid, context=ctx), } _order = 'sequence' @@ -189,6 +188,8 @@ class project(osv.osv): return res def _task_count(self, cr, uid, ids, field_name, arg, context=None): + """ :deprecated: this method will be removed with OpenERP v8. Use task_ids + fields instead. """ if context is None: context = {} res = dict.fromkeys(ids, 0) @@ -265,7 +266,10 @@ class project(osv.osv): }), 'resource_calendar_id': fields.many2one('resource.calendar', 'Working Time', help="Timetable working hours to adjust the gantt diagram report", states={'close':[('readonly',True)]} ), 'type_ids': fields.many2many('project.task.type', 'project_task_type_rel', 'project_id', 'type_id', 'Tasks Stages', states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}), - 'task_count': fields.function(_task_count, type='integer', string="Open Tasks"), + 'task_count': fields.function(_task_count, type='integer', string="Open Tasks", + deprecated="This field will be removed in OpenERP v8. Use task_ids one2many field instead."), + 'task_ids': fields.one2many('project.task', 'project_id', + domain=[('stage_id.fold', '=', False)]), 'color': fields.integer('Color Index'), 'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="restrict", required=True, help="Internal email associated with this project. Incoming emails are automatically synchronized" @@ -556,8 +560,9 @@ class task(osv.osv): _mail_post_access = 'read' _track = { 'stage_id': { - 'project.mt_task_new': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence == 1, - 'project.mt_task_stage': lambda self, cr, uid, obj, ctx=None: obj.stage_id.sequence != 1, + # this is only an heuristics; depending on your particular stage configuration it may not match all 'new' stages + 'project.mt_task_new': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence <= 1, + 'project.mt_task_stage': lambda self, cr, uid, obj, ctx=None: obj.stage_id.sequence > 1, }, 'user_id': { 'project.mt_task_assigned': lambda self, cr, uid, obj, ctx=None: obj.user_id and obj.user_id.id, @@ -582,7 +587,7 @@ class task(osv.osv): def _get_default_stage_id(self, cr, uid, context=None): """ Gives default stage_id """ project_id = self._get_default_project_id(cr, uid, context=context) - return self.stage_find(cr, uid, [], project_id, [('sequence', '=', '1')], context=context) + return self.stage_find(cr, uid, [], project_id, [('fold', '=', False)], context=context) def _resolve_project_id_from_context(self, cr, uid, context=None): """ Returns ID of project based on the value of 'default_project_id' @@ -994,7 +999,7 @@ class task(osv.osv): def set_remaining_time(self, cr, uid, ids, remaining_time=1.0, context=None): for task in self.browse(cr, uid, ids, context=context): - if (task.stage_id and task.stage_id.sequence == 1) or (task.planned_hours == 0.0): + if (task.stage_id and task.stage_id.sequence <= 1) or (task.planned_hours == 0.0): self.write(cr, uid, [task.id], {'planned_hours': remaining_time}, context=context) self.write(cr, uid, ids, {'remaining_hours': remaining_time}, context=context) return True @@ -1127,7 +1132,8 @@ class task(osv.osv): def message_new(self, cr, uid, msg, custom_values=None, context=None): """ Override to updates the document according to the email. """ - if custom_values is None: custom_values = {} + if custom_values is None: + custom_values = {} defaults = { 'name': msg.get('subject'), 'planned_hours': 0.0, @@ -1137,10 +1143,10 @@ class task(osv.osv): def message_update(self, cr, uid, ids, msg, update_vals=None, context=None): """ Override to update the task according to the email. """ - if update_vals is None: update_vals = {} - act = False + if update_vals is None: + update_vals = {} maps = { - 'cost':'planned_hours', + 'cost': 'planned_hours', } for line in msg['body'].split('\n'): line = line.strip() @@ -1153,9 +1159,7 @@ class task(osv.osv): update_vals[field] = float(res.group(2).lower()) except (ValueError, TypeError): pass - if act: - getattr(self,act)(cr, uid, ids, context=context) - return super(task,self).message_update(cr, uid, ids, msg, update_vals=update_vals, context=context) + return super(task, self).message_update(cr, uid, ids, msg, update_vals=update_vals, context=context) class project_work(osv.osv): _name = "project.task.work" @@ -1243,6 +1247,8 @@ class account_analytic_account(osv.osv): return analytic_account_id def write(self, cr, uid, ids, vals, context=None): + if isinstance(ids, (int, long)): + ids = [ids] vals_for_project = vals.copy() for account in self.browse(cr, uid, ids, context=context): if not vals.get('name'): diff --git a/addons/project/project_data.xml b/addons/project/project_data.xml index 0f48dfe9a2b..bc53c4a436f 100644 --- a/addons/project/project_data.xml +++ b/addons/project/project_data.xml @@ -1,6 +1,6 @@ - + @@ -63,14 +63,18 @@ Done + 30 Cancelled + + + Task Created diff --git a/addons/project/project_demo.xml b/addons/project/project_demo.xml index c58c61448c2..ff225c46e10 100644 --- a/addons/project/project_demo.xml +++ b/addons/project/project_demo.xml @@ -4,7 +4,7 @@ - + diff --git a/addons/project/project_view.xml b/addons/project/project_view.xml index 5015d80e01f..0f507c604f2 100644 --- a/addons/project/project_view.xml +++ b/addons/project/project_view.xml @@ -19,7 +19,7 @@ - + @@ -239,6 +239,7 @@ + @@ -258,9 +259,10 @@ %%
    @@ -683,10 +685,10 @@ - + - + diff --git a/addons/project/report/project_report.py b/addons/project/report/project_report.py index 15b26507ff3..ed086b4318d 100644 --- a/addons/project/report/project_report.py +++ b/addons/project/report/project_report.py @@ -75,7 +75,7 @@ class report_project_task_user(osv.osv): date_trunc('day',t.date_last_stage_update) as date_last_stage_update, to_date(to_char(t.date_deadline, 'dd-MM-YYYY'),'dd-MM-YYYY') as date_deadline, -- sum(cast(to_char(date_trunc('day',t.date_end) - date_trunc('day',t.date_start),'DD') as int)) as no_of_days, - abs((extract('epoch' from (t.date_end-t.date_start)))/(3600*24)) as no_of_days, + abs((extract('epoch' from (t.write_date-t.date_start)))/(3600*24)) as no_of_days, t.user_id, progress as progress, t.project_id, @@ -89,9 +89,9 @@ class report_project_task_user(osv.osv): total_hours as total_hours, t.delay_hours as hours_delay, planned_hours as hours_planned, - (extract('epoch' from (t.date_end-t.create_date)))/(3600*24) as closing_days, + (extract('epoch' from (t.write_date-t.create_date)))/(3600*24) as closing_days, (extract('epoch' from (t.date_start-t.create_date)))/(3600*24) as opening_days, - abs((extract('epoch' from (t.date_deadline-t.date_end)))/(3600*24)) as delay_endings_days + abs((extract('epoch' from (t.date_deadline-t.write_date)))/(3600*24)) as delay_endings_days FROM project_task t WHERE t.active = 'true' GROUP BY @@ -106,6 +106,7 @@ class report_project_task_user(osv.osv): month, day, create_date, + write_date, date_start, date_end, date_deadline, diff --git a/addons/project/report/project_report.xml b/addons/project/report/project_report.xml index 59d0fd28220..4ff7e852620 100644 --- a/addons/project/report/project_report.xml +++ b/addons/project/report/project_report.xml @@ -17,7 +17,6 @@ - diff --git a/addons/project_issue/project_issue.py b/addons/project_issue/project_issue.py index dc4d5cf5ff5..8e789e03658 100644 --- a/addons/project_issue/project_issue.py +++ b/addons/project_issue/project_issue.py @@ -415,18 +415,20 @@ class project_issue(osv.Model): return stage_ids[0] return False - def case_escalate(self, cr, uid, ids, context=None): - cases = self.browse(cr, uid, ids) - for case in cases: + def case_escalate(self, cr, uid, ids, context=None): # FIXME rename this method to issue_escalate + for issue in self.browse(cr, uid, ids, context=context): data = {} - if case.project_id.project_escalation_id: - data['project_id'] = case.project_id.project_escalation_id.id - if case.project_id.project_escalation_id.user_id: - data['user_id'] = case.project_id.project_escalation_id.user_id.id - if case.task_id: - self.pool.get('project.task').write(cr, uid, [case.task_id.id], {'project_id': data['project_id'], 'user_id': False}) - else: + esc_proj = issue.project_id.project_escalation_id + if not esc_proj: raise osv.except_osv(_('Warning!'), _('You cannot escalate this issue.\nThe relevant Project has not configured the Escalation Project!')) + + data['project_id'] = esc_proj.id + if esc_proj.user_id: + data['user_id'] = esc_proj.user_id.id + issue.write(data) + + if issue.task_id: + issue.task_id.write({'project_id': esc_proj.id, 'user_id': False}) return True # ------------------------------------------------------- @@ -467,40 +469,10 @@ class project_issue(osv.Model): 'partner_id': msg.get('author_id', False), 'user_id': False, } - if msg.get('priority'): - defaults['priority'] = msg.get('priority') - defaults.update(custom_values) res_id = super(project_issue, self).message_new(cr, uid, msg, custom_values=defaults, context=context) return res_id - def message_update(self, cr, uid, ids, msg, update_vals=None, context=None): - """ Overrides mail_thread message_update that is called by the mailgateway - through message_process. - This method updates the document according to the email. - """ - if isinstance(ids, (str, int, long)): - ids = [ids] - if update_vals is None: update_vals = {} - - # Update doc values according to the message - if msg.get('priority'): - update_vals['priority'] = msg.get('priority') - # Parse 'body' to find values to update - maps = { - 'cost': 'planned_cost', - 'revenue': 'planned_revenue', - 'probability': 'probability', - } - for line in msg.get('body', '').split('\n'): - line = line.strip() - res = tools.command_re.match(line) - if res and maps.get(res.group(1).lower(), False): - key = maps.get(res.group(1).lower()) - update_vals[key] = res.group(2).lower() - - return super(project_issue, self).message_update(cr, uid, ids, msg, update_vals=update_vals, context=context) - def message_post(self, cr, uid, thread_id, body='', subject=None, type='notification', subtype=None, parent_id=False, attachments=None, context=None, content_subtype='html', **kwargs): """ Overrides mail_thread message_post so that we can set the date of last action field when a new message is posted on the issue. @@ -520,6 +492,8 @@ class project(osv.Model): return [('project.task', "Tasks"), ("project.issue", "Issues")] def _issue_count(self, cr, uid, ids, field_name, arg, context=None): + """ :deprecated: this method will be removed with OpenERP v8. Use issue_ids + fields instead. """ 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): @@ -531,7 +505,10 @@ class project(osv.Model): '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', string="Unclosed Issues"), + 'issue_count': fields.function(_issue_count, type='integer', string="Unclosed Issues", + deprecated="This field will be removed in OpenERP v8. Use issue_ids one2many field instead."), + 'issue_ids': fields.one2many('project.issue', 'project_id', + domain=[('stage_id.fold', '=', False)]) } def _check_escalation(self, cr, uid, ids, context=None): diff --git a/addons/project_issue/project_issue_view.xml b/addons/project_issue/project_issue_view.xml index 445cdd17ee6..2e99b75046c 100644 --- a/addons/project_issue/project_issue_view.xml +++ b/addons/project_issue/project_issue_view.xml @@ -325,13 +325,14 @@ - + - Issues - Issue + + Issue + Issues diff --git a/addons/project_mrp/process/project_mrp_process.xml b/addons/project_mrp/process/project_mrp_process.xml index b147740758a..de2b2c1b4c8 100644 --- a/addons/project_mrp/process/project_mrp_process.xml +++ b/addons/project_mrp/process/project_mrp_process.xml @@ -50,10 +50,8 @@ `project_mrp` implements a basic procurement system for services without actually using the full-fledged procurement process from sale_stock, and without the dependency. So it stil represents a "procurement system". - TODO: To cleanup this invalid foreign external ID, the node should probably be moved to the - `sale` module directly, and removed from both `sale_stock` and `project_mrp`. --> - + subflow @@ -68,7 +66,7 @@ - + diff --git a/addons/purchase/edi/purchase_order_action_data.xml b/addons/purchase/edi/purchase_order_action_data.xml index b9c3c3ab95d..5bb56e8ab15 100644 --- a/addons/purchase/edi/purchase_order_action_data.xml +++ b/addons/purchase/edi/purchase_order_action_data.xml @@ -19,7 +19,7 @@ Purchase Order - Send by mail - ${object.validator.email or ''} + ${(object.validator.email or '')|safe} ${object.company_id.name} Order (Ref ${object.name or 'n/a' }) ${object.partner_id.id} diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index e551eb40396..fb838e8b3cd 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -1234,17 +1234,27 @@ class account_invoice(osv.Model): def invoice_validate(self, cr, uid, ids, context=None): 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) + # read access on purchase.order object is not required + if not purchase_order_obj.check_access_rights(cr, uid, 'read', raise_exception=False): + user_id = SUPERUSER_ID + else: + user_id = uid + po_ids = purchase_order_obj.search(cr, user_id, [('invoice_ids', 'in', ids)], context=context) for po_id in po_ids: - purchase_order_obj.message_post(cr, uid, po_id, body=_("Invoice received"), context=context) + purchase_order_obj.message_post(cr, user_id, po_id, 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) + # read access on purchase.order object is not required + if not purchase_order_obj.check_access_rights(cr, uid, 'read', raise_exception=False): + user_id = SUPERUSER_ID + else: + user_id = uid + po_ids = purchase_order_obj.search(cr, user_id, [('invoice_ids', 'in', ids)], context=context) if po_ids: - purchase_order_obj.message_post(cr, uid, po_ids, body=_("Invoice paid"), context=context) + purchase_order_obj.message_post(cr, user_id, po_ids, body=_("Invoice paid"), context=context) return res class account_invoice_line(osv.Model): diff --git a/addons/purchase/purchase_view.xml b/addons/purchase/purchase_view.xml index 6bed8bcbf9c..4a301d3fa7b 100644 --- a/addons/purchase/purchase_view.xml +++ b/addons/purchase/purchase_view.xml @@ -36,6 +36,9 @@ + Sales Order - Send by Email - ${object.user_id.email or ''} + ${(object.user_id.email or '')|safe} ${object.company_id.name} ${object.state in ('draft', 'sent') and 'Quotation' or 'Order'} (Ref ${object.name or 'n/a' }) ${object.partner_invoice_id.id} diff --git a/addons/sale/report/sale_report.py b/addons/sale/report/sale_report.py index d5f07f863b9..2368a9849de 100644 --- a/addons/sale/report/sale_report.py +++ b/addons/sale/report/sale_report.py @@ -70,7 +70,7 @@ class sale_report(osv.osv): t.uom_id as product_uom, sum(l.product_uom_qty / u.factor * u2.factor) as product_uom_qty, sum(l.product_uom_qty * l.price_unit * (100.0-l.discount) / 100.0) as price_total, - 1 as nbr, + count(*) as nbr, s.date_order as date, s.date_confirm as date_confirm, to_char(s.date_order, 'YYYY') as year, @@ -85,15 +85,14 @@ class sale_report(osv.osv): s.pricelist_id as pricelist_id, s.project_id as analytic_account_id from - sale_order s - join sale_order_line l on (s.id=l.order_id) + sale_order_line l + join sale_order s on (l.order_id=s.id) left join product_product p on (l.product_id=p.id) left join product_template t on (p.product_tmpl_id=t.id) left join product_uom u on (u.id=l.product_uom) left join product_uom u2 on (u2.id=t.uom_id) group by l.product_id, - l.product_uom_qty, l.order_id, t.uom_id, t.categ_id, diff --git a/addons/sale/sale.py b/addons/sale/sale.py index ad048e505e5..7a32b919eae 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -977,9 +977,9 @@ class mail_compose_message(osv.Model): def send_mail(self, cr, uid, ids, context=None): context = context or {} - if context.get('default_model') == 'sale.order' and context.get('default_res_id') and context.get('mark_so_as_sent'): + if context.get('active_model') == 'sale.order' and context.get('active_ids') and context.get('mark_so_as_sent'): context = dict(context, mail_post_autofollow=True) - self.pool.get('sale.order').signal_quotation_sent(cr, uid, [context['default_res_id']]) + self.pool.get('sale.order').signal_quotation_sent(cr, uid, context['active_ids']) return super(mail_compose_message, self).send_mail(cr, uid, ids, context=context) diff --git a/addons/sale/sale_view.xml b/addons/sale/sale_view.xml index 53c4a166977..60e5c83ecf0 100644 --- a/addons/sale/sale_view.xml +++ b/addons/sale/sale_view.xml @@ -122,7 +122,6 @@ - - +