diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index 2695db40ee6..553b6080cc6 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -637,9 +637,12 @@ class AccountBankStatementLine(models.Model): # Create The payment payment = self.env['account.payment'] + partner_id = self.partner_id or (aml_dict.get('move_line') and aml_dict['move_line'].partner_id) or self.env['res.partner'] if abs(total)>0.00001: payment_vals = self._prepare_payment_vals(total) - if self.partner_id and len(account_types) == 1: + if not payment_vals['partner_id']: + payment_vals['partner_id'] = partner_id.id + if payment_vals['partner_id'] and len(account_types) == 1: payment_vals['partner_type'] = 'customer' if account_types == receivable_account_type else 'supplier' payment = payment.create(payment_vals) diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index fd08620f992..ea88cd0275e 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -1623,12 +1623,14 @@ class AccountInvoice(models.Model): fmt = partial(formatLang, invoice.with_context(lang=invoice.partner_id.lang).env, currency_obj=currency) res = {} for line in invoice.tax_line_ids: - res.setdefault(line.tax_id.tax_group_id, {'base': 0.0, 'amount': 0.0}) - res[line.tax_id.tax_group_id]['amount'] += line.amount_total - res[line.tax_id.tax_group_id]['base'] += line.base - res = sorted(res.items(), key=lambda l: l[0].sequence) + tax = line.tax_id + group_key = (tax.tax_group_id, tax.amount_type, tax.amount) + res.setdefault(group_key, {'base': 0.0, 'amount': 0.0}) + res[group_key]['amount'] += line.amount_total + res[group_key]['base'] += line.base + res = sorted(res.items(), key=lambda l: l[0][0].sequence) invoice.amount_by_group = [( - r[0].name, r[1]['amount'], r[1]['base'], + r[0][0].name, r[1]['amount'], r[1]['base'], fmt(r[1]['amount']), fmt(r[1]['base']), len(res), ) for r in res] diff --git a/addons/account/models/account_journal_dashboard.py b/addons/account/models/account_journal_dashboard.py index f815820da4a..afc9a6cec49 100644 --- a/addons/account/models/account_journal_dashboard.py +++ b/addons/account/models/account_journal_dashboard.py @@ -172,7 +172,7 @@ class account_journal(models.Model): LEFT JOIN account_move move ON aml.move_id = move.id WHERE aml.account_id in %%s AND move.date <= %%s AND move.state = 'posted';""" % (amount_field,) - self.env.cr.execute(query, (account_ids, fields.Date.today(),)) + self.env.cr.execute(query, (account_ids, fields.Date.context_today(self),)) query_results = self.env.cr.dictfetchall() if query_results and query_results[0].get('sum') != None: account_sum = query_results[0].get('sum') diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py index 7646343f7a4..dfc2b605790 100644 --- a/addons/account/models/account_payment.py +++ b/addons/account/models/account_payment.py @@ -236,10 +236,14 @@ class account_abstract_payment(models.AbstractModel): currency = self.currency_id or self.journal_id.currency_id or self.journal_id.company_id.currency_id or invoices and invoices[0].currency_id # Avoid currency rounding issues by summing the amounts according to the company_currency_id before + invoice_datas = invoices.read_group( + [('id', 'in', invoices.ids)], + ['currency_id', 'type', 'residual_signed'], + ['currency_id', 'type'], lazy=False) total = 0.0 - groups = groupby(invoices, lambda i: i.currency_id) - for payment_currency, payment_invoices in groups: - amount_total = sum([MAP_INVOICE_TYPE_PAYMENT_SIGN[i.type] * i.residual_signed for i in payment_invoices]) + for invoice_data in invoice_datas: + amount_total = MAP_INVOICE_TYPE_PAYMENT_SIGN[invoice_data['type']] * invoice_data['residual_signed'] + payment_currency = self.env['res.currency'].browse(invoice_data['currency_id'][0]) if payment_currency == currency: total += amount_total else: diff --git a/addons/account/models/account_reconcile_model.py b/addons/account/models/account_reconcile_model.py index b3e7dbf6cc3..83aa46b8e0d 100644 --- a/addons/account/models/account_reconcile_model.py +++ b/addons/account/models/account_reconcile_model.py @@ -574,7 +574,7 @@ class AccountReconcileModel(models.Model): if line_residual > total_residual: amount_percentage = (total_residual / line_residual) * 100 else: - amount_percentage = (line_residual / total_residual) * 100 + amount_percentage = (line_residual / total_residual) * 100 if total_residual else 0 return amount_percentage >= self.match_total_amount_param @api.multi diff --git a/addons/account/models/reconciliation_widget.py b/addons/account/models/reconciliation_widget.py index dd8ad313ca4..c37953f60d6 100644 --- a/addons/account/models/reconciliation_widget.py +++ b/addons/account/models/reconciliation_widget.py @@ -143,11 +143,12 @@ class AccountReconciliation(models.AbstractModel): else: aml_ids = matching_amls[line.id]['aml_ids'] bank_statements_left += line.statement_id + target_currency = line.currency_id or line.journal_id.currency_id or line.journal_id.company_id.currency_id amls = aml_ids and self.env['account.move.line'].browse(aml_ids) line_vals = { 'st_line': self._get_statement_line(line), - 'reconciliation_proposition': aml_ids and self._prepare_move_lines(amls) or [], + 'reconciliation_proposition': aml_ids and self._prepare_move_lines(amls, target_currency=target_currency, target_date=line.date) or [], 'model_id': matching_amls[line.id].get('model') and matching_amls[line.id]['model'].id, 'write_off': matching_amls[line.id].get('status') == 'write_off', } @@ -485,21 +486,12 @@ class AccountReconciliation(models.AbstractModel): ('payment_id', '<>', False) ] - # Black lines = unreconciled & (not linked to a payment or open balance created by statement - domain_matching = [('reconciled', '=', False)] - if partner_id: - domain_matching = expression.AND([ - domain_matching, - [('account_id.internal_type', 'in', ['payable', 'receivable'])] - ]) - else: - # TODO : find out what use case this permits (match a check payment, registered on a journal whose account type is other instead of liquidity) - domain_matching = expression.AND([ - domain_matching, - [('account_id.reconcile', '=', True)] - ]) + # default domain matching + domain_matching = expression.AND([ + [('reconciled', '=', False)], + [('account_id.reconcile', '=', True)] + ]) - # Let's add what applies to both domain = expression.OR([domain_reconciliation, domain_matching]) if partner_id: domain = expression.AND([domain, [('partner_id', '=', partner_id)]]) diff --git a/addons/account/tests/test_reconciliation_widget.py b/addons/account/tests/test_reconciliation_widget.py index 7b5f21ee325..a24cec75ca5 100644 --- a/addons/account/tests/test_reconciliation_widget.py +++ b/addons/account/tests/test_reconciliation_widget.py @@ -2,6 +2,7 @@ import logging import odoo.tests import time import requests +from odoo.addons.account.tests.test_reconciliation import TestReconciliation _logger = logging.getLogger(__name__) @@ -27,3 +28,29 @@ class TestUi(odoo.tests.HttpCase): self.phantom_js(prep.url.replace('http://localhost','').replace('?','#'), "odoo.__DEBUG__.services['web_tour.tour'].run('bank_statement_reconciliation')", "odoo.__DEBUG__.services['web_tour.tour'].tours.bank_statement_reconciliation.ready", login="admin") + + +@odoo.tests.tagged('post_install', '-at_install') +class TestReconciliationWidget(TestReconciliation): + + def test_statement_suggestion_other_currency(self): + # company currency is EUR + # payment in USD + invoice = self.create_invoice(invoice_amount=50, currency_id=self.currency_usd_id) + + # journal currency in USD + bank_stmt = self.acc_bank_stmt_model.create({ + 'journal_id': self.bank_journal_usd.id, + 'date': time.strftime('%Y-07-15'), + 'name': 'payment %s' % invoice.number, + }) + + bank_stmt_line = self.acc_bank_stmt_line_model.create({'name': 'payment', + 'statement_id': bank_stmt.id, + 'partner_id': self.partner_agrolait_id, + 'amount': 50, + 'date': time.strftime('%Y-07-15'), + }) + + result = self.env['account.reconciliation.widget'].get_bank_statement_line_data(bank_stmt_line.ids) + self.assertEqual(result['lines'][0]['reconciliation_proposition'][0]['amount_str'], '$ 50.00') \ No newline at end of file diff --git a/addons/account/views/account_payment_view.xml b/addons/account/views/account_payment_view.xml index 135c735eecc..7dbbd1aa023 100644 --- a/addons/account/views/account_payment_view.xml +++ b/addons/account/views/account_payment_view.xml @@ -71,7 +71,7 @@
- +
diff --git a/addons/account/views/account_view.xml b/addons/account/views/account_view.xml index 500028bfcf5..c0abc40d236 100644 --- a/addons/account/views/account_view.xml +++ b/addons/account/views/account_view.xml @@ -1553,7 +1553,7 @@
- +
diff --git a/addons/account/views/report_invoice.xml b/addons/account/views/report_invoice.xml index 75140895390..bd55957c7c1 100644 --- a/addons/account/views/report_invoice.xml +++ b/addons/account/views/report_invoice.xml @@ -84,7 +84,7 @@ - + diff --git a/addons/analytic/models/analytic_account.py b/addons/analytic/models/analytic_account.py index 187d53d1ad7..91815380304 100644 --- a/addons/analytic/models/analytic_account.py +++ b/addons/analytic/models/analytic_account.py @@ -160,7 +160,10 @@ class AccountAnalyticAccount(models.Model): if operator == 'ilike' and not (name or '').strip(): domain = [] else: - domain = ['|', '|', ('code', operator, name), ('name', operator, name), ('partner_id.name', operator, name)] + # `partner_id` is in auto_join and the searches using ORs with auto_join fields doesn't work + # we have to cut the search in two searches ... https://github.com/odoo/odoo/issues/25175 + partner_ids = self.env['res.partner']._search([('name', operator, name)], limit=limit, access_rights_uid=name_get_uid) + domain = ['|', '|', ('code', operator, name), ('name', operator, name), ('partner_id', 'in', partner_ids)] analytic_account_ids = self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid) return self.browse(analytic_account_ids).name_get() diff --git a/addons/auth_signup/models/res_partner.py b/addons/auth_signup/models/res_partner.py index 85d6d732055..db66467d7dd 100644 --- a/addons/auth_signup/models/res_partner.py +++ b/addons/auth_signup/models/res_partner.py @@ -27,16 +27,16 @@ class ResPartner(models.Model): signup_token = fields.Char(copy=False, groups="base.group_erp_manager") signup_type = fields.Char(string='Signup Token Type', copy=False, groups="base.group_erp_manager") signup_expiration = fields.Datetime(copy=False, groups="base.group_erp_manager") - signup_valid = fields.Boolean(compute='_compute_signup_valid', compute_sudo=True, string='Signup Token is Valid') + signup_valid = fields.Boolean(compute='_compute_signup_valid', string='Signup Token is Valid') signup_url = fields.Char(compute='_compute_signup_url', string='Signup URL') @api.multi @api.depends('signup_token', 'signup_expiration') def _compute_signup_valid(self): dt = now() - for partner in self: - partner.signup_valid = bool(partner.signup_token) and \ - (not partner.signup_expiration or dt <= partner.signup_expiration) + for partner, partner_sudo in zip(self, self.sudo()): + partner.signup_valid = bool(partner_sudo.signup_token) and \ + (not partner_sudo.signup_expiration or dt <= partner_sudo.signup_expiration) @api.multi def _compute_signup_url(self): diff --git a/addons/base_automation/data/base_automation_demo.xml b/addons/base_automation/data/base_automation_demo.xml index 246320ecf4a..5ffdc94d6a3 100644 --- a/addons/base_automation/data/base_automation_demo.xml +++ b/addons/base_automation/data/base_automation_demo.xml @@ -71,4 +71,19 @@ if 'user_id' in env.context['old_values'][record.id]: + + Template Automation + + <div>Email automation</div> + + + Base Automation: test send an email + + + email + on_write + + [('deadline', '!=', False)] + [('deadline', '=', False)] + diff --git a/addons/base_automation/models/base_automation.py b/addons/base_automation/models/base_automation.py index ac3e6305c6b..a482d7d5911 100644 --- a/addons/base_automation/models/base_automation.py +++ b/addons/base_automation/models/base_automation.py @@ -161,14 +161,17 @@ class BaseAutomation(models.Model): return records def _filter_post(self, records): + return self._filter_post_export_domain(records)[0] + + def _filter_post_export_domain(self, records): """ Filter the records that satisfy the postcondition of action ``self``. """ if self.filter_domain and records: domain = [('id', 'in', records.ids)] + safe_eval(self.filter_domain, self._get_eval_context()) - return records.search(domain) + return records.search(domain), domain else: - return records + return records, None - def _process(self, records): + def _process(self, records, domain_post=None): """ Process action ``self`` on the ``records`` that have not been done yet. """ # filter out the records on which self has already been done action_done = self._context['__action_done'] @@ -193,7 +196,12 @@ class BaseAutomation(models.Model): # execute server actions if self.action_server_id: for record in records: - ctx = {'active_model': record._name, 'active_ids': record.ids, 'active_id': record.id} + ctx = { + 'active_model': record._name, + 'active_ids': record.ids, + 'active_id': record.id, + 'domain_post': domain_post, + } self.action_server_id.with_context(**ctx).run() @api.model_cr @@ -247,7 +255,8 @@ class BaseAutomation(models.Model): _write.origin(records, vals, **kw) # check postconditions, and execute actions on the records that satisfy them for action in actions.with_context(old_values=old_values): - action._process(action._filter_post(pre[action])) + records, domain_post = action._filter_post_export_domain(pre[action]) + action._process(records, domain_post=domain_post) return True return _write diff --git a/addons/base_automation/tests/test_base_automation.py b/addons/base_automation/tests/test_base_automation.py index d71423f4e69..c3fcf6e7ad5 100644 --- a/addons/base_automation/tests/test_base_automation.py +++ b/addons/base_automation/tests/test_base_automation.py @@ -1,5 +1,6 @@ # # -*- coding: utf-8 -*- # # Part of Odoo. See LICENSE file for full copyright and licensing details. +from unittest.mock import patch from odoo.tests import common @@ -9,13 +10,14 @@ class base_automation_test(common.TransactionCase): def setUp(self): super(base_automation_test, self).setUp() - self.user_admin = self.env.ref('base.user_root') + self.user_root = self.env.ref('base.user_root') + self.user_admin = self.env.ref('base.user_admin') self.user_demo = self.env.ref('base.user_demo') def create_lead(self, **kwargs): vals = { 'name': "Lead Test", - 'user_id': self.user_admin.id, + 'user_id': self.user_root.id, } vals.update(kwargs) return self.env['base.automation.lead.test'].create(vals) @@ -27,7 +29,7 @@ class base_automation_test(common.TransactionCase): """ lead = self.create_lead(state='open') self.assertEqual(lead.state, 'open') - self.assertEqual(lead.user_id, self.user_admin, "Responsible should not change on creation of Lead with state 'open'.") + self.assertEqual(lead.user_id, self.user_root, "Responsible should not change on creation of Lead with state 'open'.") def test_01_check_to_state_draft_post(self): """ @@ -49,15 +51,15 @@ class base_automation_test(common.TransactionCase): """ lead = self.create_lead(state='open') self.assertEqual(lead.state, 'open', "Lead state should be 'open'") - self.assertEqual(lead.user_id, self.user_admin, "Responsible should not change on creation of Lead with state 'open'.") + self.assertEqual(lead.user_id, self.user_root, "Responsible should not change on creation of Lead with state 'open'.") # change state to pending and check that responsible has not changed lead.write({'state': 'pending'}) self.assertEqual(lead.state, 'pending', "Lead state should be 'pending'") - self.assertEqual(lead.user_id, self.user_admin, "Responsible should not change on creation of Lead with state from 'draft' to 'open'.") + self.assertEqual(lead.user_id, self.user_root, "Responsible should not change on creation of Lead with state from 'draft' to 'open'.") # change state to done and check that responsible has not changed lead.write({'state': 'done'}) self.assertEqual(lead.state, 'done', "Lead state should be 'done'") - self.assertEqual(lead.user_id, self.user_admin, "Responsible should not chang on creation of Lead with state from 'pending' to 'done'.") + self.assertEqual(lead.user_id, self.user_root, "Responsible should not chang on creation of Lead with state from 'pending' to 'done'.") def test_03_check_from_draft_to_done_without_steps(self): """ @@ -70,7 +72,7 @@ class base_automation_test(common.TransactionCase): """ lead = self.create_lead(state='open') self.assertEqual(lead.state, 'open', "Lead state should be 'open'") - self.assertEqual(lead.user_id, self.user_admin, "Responsible should not change on creation of Lead with state 'open'.") + self.assertEqual(lead.user_id, self.user_root, "Responsible should not change on creation of Lead with state 'open'.") # change state to done and check that responsible has changed lead.write({'state': 'done'}) self.assertEqual(lead.state, 'done', "Lead state should be 'done'") @@ -85,7 +87,7 @@ class base_automation_test(common.TransactionCase): partner.write({'customer': False}) lead = self.create_lead(state='open', partner_id=partner.id) self.assertFalse(lead.customer, "Customer field should updated to False") - self.assertEqual(lead.user_id, self.user_admin, "Responsible should not change on creation of Lead with state from 'draft' to 'open'.") + self.assertEqual(lead.user_id, self.user_root, "Responsible should not change on creation of Lead with state from 'draft' to 'open'.") # change partner, recompute on lead should trigger the rule partner.write({'customer': True}) self.assertTrue(lead.customer, "Customer field should updated to True") @@ -101,15 +103,52 @@ class base_automation_test(common.TransactionCase): self.assertFalse(lead.deadline, 'There should not be a deadline defined') # change priority and user; this triggers deadline recomputation, and # the server action should set the boolean field to True - lead.write({'priority': True, 'user_id': self.user_admin.id}) + lead.write({'priority': True, 'user_id': self.user_root.id}) self.assertTrue(lead.deadline, 'Deadline should be defined') self.assertTrue(lead.is_assigned_to_admin, 'Lead should be assigned to admin') + def test_11b_recomputed_field(self): + mail_automation = self.env.ref('base_automation.test_rule_on_write_recompute_send_email') + send_mail_count = 0 + + def _patched_get_actions(*args, **kwargs): + obj = args[0] + if '__action_done' not in obj._context: + obj = obj.with_context(__action_done={}) + return mail_automation.with_env(obj.env) + + def _patched_send_mail(*args, **kwargs): + nonlocal send_mail_count + send_mail_count += 1 + + patchers = [ + patch('odoo.addons.base_automation.models.base_automation.BaseAutomation._get_actions', _patched_get_actions), + patch('odoo.addons.mail.models.mail_template.MailTemplate.send_mail', _patched_send_mail), + ] + + patchers[0].start() + + lead = self.create_lead() + self.assertFalse(lead.priority) + self.assertFalse(lead.deadline) + + patchers[1].start() + + lead.write({'priority': True}) + + self.assertTrue(lead.priority) + self.assertTrue(lead.deadline) + + for patcher in patchers: + patcher.stop() + + self.assertEqual(send_mail_count, 1) + def test_12_recursive(self): """ Check that a rule is executed recursively by a secondary change. """ lead = self.create_lead(state='open') self.assertEqual(lead.state, 'open') - self.assertEqual(lead.user_id, self.user_admin) + self.assertEqual(lead.user_id, self.user_root) # change partner; this should trigger the rule that modifies the state partner = self.env.ref('base.res_partner_1') lead.write({'partner_id': partner.id}) diff --git a/addons/base_import/static/csv/o2m_customers_contacts.csv b/addons/base_import/static/csv/o2m_customers_contacts.csv index bf142142d3c..d29a600f18b 100644 --- a/addons/base_import/static/csv/o2m_customers_contacts.csv +++ b/addons/base_import/static/csv/o2m_customers_contacts.csv @@ -1,9 +1,9 @@ Name,Is a company,Related company,Address type,Customer,Supplier,Street,ZIP,City,State,Country -Aurora Shelves,1,,,1,0,25 Pacific Road,95101,San José,CA,United States -Roger Martins,0,Aurora Shelves,Invoice address,1,0,27 Pacific Road,95102,San José,CA,United States -House Sales Direct,1,,,1,0,104 Saint Mary Avenue,94059,Redwood,CA,United States -Yvan Holiday,0,House Sales Direct,Contact,1,0,104 Saint Mary Avenue,94060,Redwood,CA,United States -Jack Unsworth,0,House Sales Direct,Invoice address,1,0,227 Jackson Road,94061,Redwood,CA,United States -Michael Mason,0,,,1,0,16 5th Avenue,94104,San Francisco,CA,United States -International Wood,1,,,1,0,748 White House Boulevard,20004,Washington,DC,United States -Sharon Pecker,0,International Wood,Invoice address,1,0,755 White House Boulevard,20005,Washington,DC,United States +Aurora Shelves,1,,,1,0,25 Pacific Road,95101,San José,California,United States +Roger Martins,0,Aurora Shelves,Invoice address,1,0,27 Pacific Road,95102,San José,California,United States +House Sales Direct,1,,,1,0,104 Saint Mary Avenue,94059,Redwood,California,United States +Yvan Holiday,0,House Sales Direct,Contact,1,0,104 Saint Mary Avenue,94060,Redwood,California,United States +Jack Unsworth,0,House Sales Direct,Invoice address,1,0,227 Jackson Road,94061,Redwood,California,United States +Michael Mason,0,,,1,0,16 5th Avenue,94104,San Francisco,California,United States +International Wood,1,,,1,0,748 White House Boulevard,20004,Washington,D.C.,United States +Sharon Pecker,0,International Wood,Invoice address,1,0,755 White House Boulevard,20005,Washington,D.C.,United States diff --git a/addons/bus/static/src/js/longpolling_bus.js b/addons/bus/static/src/js/longpolling_bus.js index 50058bc8ae4..83f2b0676ef 100644 --- a/addons/bus/static/src/js/longpolling_bus.js +++ b/addons/bus/static/src/js/longpolling_bus.js @@ -32,30 +32,33 @@ var LongpollingBus = Bus.extend(ServicesMixin, { init: function (parent, params) { this._super.apply(this, arguments); this._id = _.uniqueId('bus'); + + // the _id is modified by crosstab_bus, so we can't use it to unbind the events in the destroy. + this._longPollingBusId = this._id; this._options = {}; this._channels = []; // bus presence this._lastPresenceTime = new Date().getTime(); - $(window).on("focus." + this._id, this._onFocusChange.bind(this, {focus: true})); - $(window).on("blur." + this._id, this._onFocusChange.bind(this, {focus: false})); - $(window).on("unload." + this._id, this._onFocusChange.bind(this, {focus: false})); + $(window).on("focus." + this._longPollingBusId, this._onFocusChange.bind(this, {focus: true})); + $(window).on("blur." + this._longPollingBusId, this._onFocusChange.bind(this, {focus: false})); + $(window).on("unload." + this._longPollingBusId, this._onFocusChange.bind(this, {focus: false})); - $(window).on("click." + this._id, this._onPresence.bind(this)); - $(window).on("keydown." + this._id, this._onPresence.bind(this)); - $(window).on("keyup." + this._id, this._onPresence.bind(this)); + $(window).on("click." + this._longPollingBusId, this._onPresence.bind(this)); + $(window).on("keydown." + this._longPollingBusId, this._onPresence.bind(this)); + $(window).on("keyup." + this._longPollingBusId, this._onPresence.bind(this)); }, /** * @override */ destroy: function () { this.stopPolling(); - $(window).off("focus." + this._id); - $(window).off("blur." + this._id); - $(window).off("unload." + this._id); - $(window).off("click." + this._id); - $(window).off("keydown." + this._id); - $(window).off("keyup." + this._id); + $(window).off("focus." + this._longPollingBusId); + $(window).off("blur." + this._longPollingBusId); + $(window).off("unload." + this._longPollingBusId); + $(window).off("click." + this._longPollingBusId); + $(window).off("keydown." + this._longPollingBusId); + $(window).off("keyup." + this._longPollingBusId); this._super(); }, //-------------------------------------------------------------------------- diff --git a/addons/crm/models/crm_team.py b/addons/crm/models/crm_team.py index 30d7cc945ea..a101fb26848 100644 --- a/addons/crm/models/crm_team.py +++ b/addons/crm/models/crm_team.py @@ -31,7 +31,10 @@ class Team(models.Model): compute='_compute_overdue_opportunities', string='Overdue Opportunities Revenues') - alias_user_id = fields.Many2one(domain=lambda self: [ + # Since we are in a _inherits case, this is not an override + # but a plain definition of a field + # So we need to reset the property related of that field + alias_user_id = fields.Many2one('res.users', related='alias_id.alias_user_id', inherited=True, domain=lambda self: [ ('groups_id', 'in', self.env.ref('sales_team.group_sale_salesman_all_leads').id)]) def _compute_unassigned_leads_count(self): diff --git a/addons/event_sale/models/sale_order.py b/addons/event_sale/models/sale_order.py index 1b15093de03..fac1730bead 100644 --- a/addons/event_sale/models/sale_order.py +++ b/addons/event_sale/models/sale_order.py @@ -73,6 +73,11 @@ class SaleOrderLine(models.Model): if self.event_ticket_id and (not self.event_id or self.event_id != self.event_ticket_id.event_id): self.event_ticket_id = None + @api.onchange('product_uom', 'product_uom_qty') + def product_uom_change(self): + if not self.event_ticket_id: + super(SaleOrderLine, self).product_uom_change() + @api.onchange('event_ticket_id') def _onchange_event_ticket_id(self): company = self.event_id.company_id or self.env.user.company_id diff --git a/addons/gamification/models/badge.py b/addons/gamification/models/badge.py index 849f1744f46..f092a6dd522 100644 --- a/addons/gamification/models/badge.py +++ b/addons/gamification/models/badge.py @@ -259,7 +259,7 @@ class GamificationBadge(models.Model): elif self.rule_auth == 'users' and self.env.user not in self.rule_auth_user_ids: return self.USER_NOT_VIP elif self.rule_auth == 'having': - all_user_badges = self.env['gamification.badge.user'].search([('user_id', '=', self.env.uid)]) + all_user_badges = self.env['gamification.badge.user'].search([('user_id', '=', self.env.uid)]).mapped('badge_id') if self.rule_auth_badge_ids - all_user_badges: return self.BADGE_REQUIRED diff --git a/addons/hr_fleet/controllers/i18n/az.po b/addons/hr_fleet/controllers/i18n/az.po deleted file mode 100644 index 23fa2458e4d..00000000000 --- a/addons/hr_fleet/controllers/i18n/az.po +++ /dev/null @@ -1,115 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * crm_project -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server saas~11.5\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-09-21 13:17+0000\n" -"PO-Revision-Date: 2018-08-24 09:17+0000\n" -"Language-Team: Azerbaijani (https://www.transifex.com/odoo/teams/41243/az/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Language: az\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" - -#. module: crm_project -#: model_terms:ir.ui.view,arch_db:crm_project.crm_lead_view_form -msgid "" -"Convert To\n" -" Task" -msgstr "" - -#. module: crm_project -#: model_terms:ir.ui.view,arch_db:crm_project.crm_lead_convert2task_view_form -msgid "Cancel" -msgstr "" - -#. module: crm_project -#: model:ir.actions.act_window,name:crm_project.crm_lead_convert2task_action -#: model_terms:ir.ui.view,arch_db:crm_project.crm_lead_convert2task_view_form -#: model_terms:ir.ui.view,arch_db:crm_project.crm_lead_view_form -msgid "Convert to Task" -msgstr "" - -#. module: crm_project -#: model_terms:ir.ui.view,arch_db:crm_project.crm_lead_convert2task_view_form -msgid "Create Task" -msgstr "" - -#. module: crm_project -#: selection:crm.lead.convert2task,action:0 -msgid "Create a new customer" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__create_uid -msgid "Created by" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__create_date -msgid "Created on" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__partner_id -msgid "Customer" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__display_name -msgid "Display Name" -msgstr "" - -#. module: crm_project -#: selection:crm.lead.convert2task,action:0 -msgid "Do not link to a customer" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__id -msgid "ID" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task____last_update -msgid "Last Modified on" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__write_uid -msgid "Last Updated by" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__write_date -msgid "Last Updated on" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__lead_id -msgid "Lead" -msgstr "" - -#. module: crm_project -#: model:ir.model,name:crm_project.model_crm_lead_convert2task -msgid "Lead convert to Task" -msgstr "" - -#. module: crm_project -#: selection:crm.lead.convert2task,action:0 -msgid "Link to an existing customer" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__project_id -msgid "Project" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__action -msgid "Related Customer" -msgstr "" diff --git a/addons/hr_fleet/controllers/i18n/fil.po b/addons/hr_fleet/controllers/i18n/fil.po deleted file mode 100644 index f3c56bf488c..00000000000 --- a/addons/hr_fleet/controllers/i18n/fil.po +++ /dev/null @@ -1,115 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * crm_project -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server saas~11.5\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-09-21 13:17+0000\n" -"PO-Revision-Date: 2018-08-24 09:17+0000\n" -"Language-Team: Filipino (https://www.transifex.com/odoo/teams/41243/fil/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Language: fil\n" -"Plural-Forms: nplurals=2; plural=(n == 1 || n==2 || n==3) || (n % 10 != 4 || n % 10 != 6 || n % 10 != 9);\n" - -#. module: crm_project -#: model_terms:ir.ui.view,arch_db:crm_project.crm_lead_view_form -msgid "" -"Convert To\n" -" Task" -msgstr "" - -#. module: crm_project -#: model_terms:ir.ui.view,arch_db:crm_project.crm_lead_convert2task_view_form -msgid "Cancel" -msgstr "" - -#. module: crm_project -#: model:ir.actions.act_window,name:crm_project.crm_lead_convert2task_action -#: model_terms:ir.ui.view,arch_db:crm_project.crm_lead_convert2task_view_form -#: model_terms:ir.ui.view,arch_db:crm_project.crm_lead_view_form -msgid "Convert to Task" -msgstr "" - -#. module: crm_project -#: model_terms:ir.ui.view,arch_db:crm_project.crm_lead_convert2task_view_form -msgid "Create Task" -msgstr "" - -#. module: crm_project -#: selection:crm.lead.convert2task,action:0 -msgid "Create a new customer" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__create_uid -msgid "Created by" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__create_date -msgid "Created on" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__partner_id -msgid "Customer" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__display_name -msgid "Display Name" -msgstr "" - -#. module: crm_project -#: selection:crm.lead.convert2task,action:0 -msgid "Do not link to a customer" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__id -msgid "ID" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task____last_update -msgid "Last Modified on" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__write_uid -msgid "Last Updated by" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__write_date -msgid "Last Updated on" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__lead_id -msgid "Lead" -msgstr "" - -#. module: crm_project -#: model:ir.model,name:crm_project.model_crm_lead_convert2task -msgid "Lead convert to Task" -msgstr "" - -#. module: crm_project -#: selection:crm.lead.convert2task,action:0 -msgid "Link to an existing customer" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__project_id -msgid "Project" -msgstr "" - -#. module: crm_project -#: model:ir.model.fields,field_description:crm_project.field_crm_lead_convert2task__action -msgid "Related Customer" -msgstr "" diff --git a/addons/hr_gamification/wizard/gamification_badge_user_wizard.py b/addons/hr_gamification/wizard/gamification_badge_user_wizard.py index 4e486a0e048..7c4c174129f 100644 --- a/addons/hr_gamification/wizard/gamification_badge_user_wizard.py +++ b/addons/hr_gamification/wizard/gamification_badge_user_wizard.py @@ -10,7 +10,7 @@ class GamificationBadgeUserWizard(models.TransientModel): employee_id = fields.Many2one('hr.employee', string='Employee', required=True) user_id = fields.Many2one('res.users', string='User', related='employee_id.user_id', - store=False, readonly=True) + store=False, readonly=True, compute_sudo=True) @api.multi def action_grant_badge(self): diff --git a/addons/hr_holidays/models/hr_leave.py b/addons/hr_holidays/models/hr_leave.py index be18aaa71d6..395bc0e72c6 100644 --- a/addons/hr_holidays/models/hr_leave.py +++ b/addons/hr_holidays/models/hr_leave.py @@ -111,13 +111,12 @@ class HolidaysRequest(models.Model): "\nThe status is 'Approved', when time off request is approved by manager.") payslip_status = fields.Boolean('Reported in last payslips', help='Green this button when the time off has been taken into account in the payslip.') report_note = fields.Text('HR Comments') - user_id = fields.Many2one('res.users', string='User', related='employee_id.user_id', related_sudo=True, store=True, default=lambda self: self.env.uid, readonly=True) + user_id = fields.Many2one('res.users', string='User', related='employee_id.user_id', related_sudo=True, compute_sudo=True, store=True, default=lambda self: self.env.uid, readonly=True) # leave type configuration holiday_status_id = fields.Many2one( "hr.leave.type", string="Time Off Type", required=True, readonly=True, states={'draft': [('readonly', False)], 'confirm': [('readonly', False)]}, domain=[('valid', '=', True)]) - leave_type_request_unit = fields.Selection(related='holiday_status_id.request_unit', readonly=True) validation_type = fields.Selection('Validation Type', related='holiday_status_id.validation_type', readonly=False) # HR data employee_id = fields.Many2one( diff --git a/addons/hr_holidays/models/hr_leave_allocation.py b/addons/hr_holidays/models/hr_leave_allocation.py index 8be3c4ac604..aecd6d7f5ed 100644 --- a/addons/hr_holidays/models/hr_leave_allocation.py +++ b/addons/hr_holidays/models/hr_leave_allocation.py @@ -252,7 +252,7 @@ class HolidaysAllocation(models.Model): if self.env.user.employee_ids: self.department_id = self.department_id or self.env.user.employee_ids[0].department_id self.employee_id = None - elif self.holiday_type == 'category': + else: self.employee_id = None self.department_id = None diff --git a/addons/hr_holidays/report/hr_leave_reports.xml b/addons/hr_holidays/report/hr_leave_reports.xml index 90e1affc20c..f7da796a104 100644 --- a/addons/hr_holidays/report/hr_leave_reports.xml +++ b/addons/hr_holidays/report/hr_leave_reports.xml @@ -77,7 +77,7 @@
- +
diff --git a/addons/hr_holidays/security/hr_holidays_security.xml b/addons/hr_holidays/security/hr_holidays_security.xml index df7317fa5d2..479c504f048 100644 --- a/addons/hr_holidays/security/hr_holidays_security.xml +++ b/addons/hr_holidays/security/hr_holidays_security.xml @@ -3,8 +3,9 @@ Team Leader - - + + + Officer @@ -44,7 +45,7 @@ Time Off: Team Leader: write on employee's time off in his team - [('employee_id.leave_manager_id', '=' , user.id)] + ['|', ('employee_id.leave_manager_id', '=' , user.id), ('employee_id.parent_id.user_id', '=' , user.id)] @@ -129,6 +130,16 @@ + + Allocations: Team Leader: read employee's allocations in his team + + ['|', ('employee_id.leave_manager_id', '=' , user.id), ('employee_id.parent_id.user_id', '=' , user.id)] + + + + + + Time Off Resources: Team Leader diff --git a/addons/hr_holidays/tests/common.py b/addons/hr_holidays/tests/common.py index 0f9c6f1cc89..510c6883b41 100644 --- a/addons/hr_holidays/tests/common.py +++ b/addons/hr_holidays/tests/common.py @@ -23,6 +23,9 @@ class TestHrHolidaysBase(common.TransactionCase): self.user_hrmanager_2 = mail_new_test_user(self.env, login='florence', groups='base.group_user,hr_holidays.group_hr_holidays_manager') self.user_hrmanager_2_id = self.user_hrmanager_2.id + self.user_team_leader = mail_new_test_user(self.env, login='arthur', groups='base.group_user,hr_holidays.group_hr_holidays_team_leader') + self.user_team_leader_id = self.user_team_leader.id + # Hr Data Department = self.env['hr.department'].with_context(tracking_disable=True) diff --git a/addons/hr_holidays/tests/test_access_rights.py b/addons/hr_holidays/tests/test_access_rights.py index ca651a69010..00ac2856d20 100644 --- a/addons/hr_holidays/tests/test_access_rights.py +++ b/addons/hr_holidays/tests/test_access_rights.py @@ -362,6 +362,37 @@ class TestLeavesRights(TestHrHolidaysBase): self.employee_leave.sudo(self.user_hruser).action_approve() self.employee_leave.sudo(self.user_hrmanager_id).action_validate() + # ---------------------------------------- + # Validation: team leader + # ---------------------------------------- + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_validation_team_leader_other(self): + """ Team Leader validates its employee's leave only""" + self.leave_type.write({'allocation_type': 'fixed'}) + self.env['hr.leave.allocation'].create({ + 'holiday_status_id': self.leave_type.id, + 'employee_id': self.employee_emp.id, + 'number_of_days': 10, + }).action_approve() + with self.assertRaises(AccessError): + self.employee_leave.sudo(self.user_team_leader).action_approve() + + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + def test_leave_validation_team_leader(self): + """ Team Leader validates its employee's leave""" + self.leave_type.write({'allocation_type': 'fixed'}) + + # set the team leader + self.employee_emp.leave_manager_id = self.user_team_leader + + self.env['hr.leave.allocation'].create({ + 'holiday_status_id': self.leave_type.id, + 'employee_id': self.employee_emp.id, + 'number_of_days': 10, + }).action_approve() + self.employee_leave.sudo(self.user_team_leader).action_approve() + class TestMultiCompany(TestHrHolidaysBase): diff --git a/addons/hr_holidays/tests/test_leave_requests.py b/addons/hr_holidays/tests/test_leave_requests.py index 67d468b0a44..ea025d571cd 100644 --- a/addons/hr_holidays/tests/test_leave_requests.py +++ b/addons/hr_holidays/tests/test_leave_requests.py @@ -138,7 +138,7 @@ class TestLeaveRequests(TestHrHolidaysBase): 'number_of_days': 1, }) - @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail') + @mute_logger('odoo.models.unlink', 'odoo.addons.mail.models.mail_mail', 'odoo.models') def test_accrual_validity_time_not_valid(self): """ Employee ask leav during a not valid validity time """ with self.assertRaises(ValidationError): diff --git a/addons/l10n_be_hr_payroll/views/res_config_settings_views.xml b/addons/l10n_be_hr_payroll/views/res_config_settings_views.xml index f65c72e610e..947cfbb98f7 100644 --- a/addons/l10n_be_hr_payroll/views/res_config_settings_views.xml +++ b/addons/l10n_be_hr_payroll/views/res_config_settings_views.xml @@ -18,52 +18,52 @@