diff --git a/addons/account/models/account.py b/addons/account/models/account.py index bb86b8a56ac..6b57858b6b8 100644 --- a/addons/account/models/account.py +++ b/addons/account/models/account.py @@ -311,6 +311,12 @@ class AccountAccount(models.Model): self.filtered(lambda r: not r.reconcile)._toggle_reconcile_to_true() else: self.filtered(lambda r: r.reconcile)._toggle_reconcile_to_false() + + if vals.get('currency_id'): + for account in self: + if self.env['account.move.line'].search_count([('account_id', '=', account.id), ('currency_id', 'not in', (False, vals['currency_id']))]): + raise UserError(_('You cannot set a currency on this account as it already has some journal entries having a different foreign currency.')) + return super(AccountAccount, self).write(vals) @api.multi @@ -1128,6 +1134,7 @@ class AccountTax(models.Model): 'refund_account_id': tax.refund_account_id.id, 'analytic': tax.analytic, 'price_include': tax.price_include, + 'tax_exigibility': tax.tax_exigibility, }) return { diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 23dccf27b36..156cf51a172 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -1441,18 +1441,7 @@ class AccountInvoice(models.Model): new_invoices += refund_invoice return new_invoices - @api.multi - def pay_and_reconcile(self, pay_journal, pay_amount=None, date=None, writeoff_acc=None): - """ Create and post an account.payment for the invoice self, which creates a journal entry that reconciles the invoice. - - :param pay_journal: journal in which the payment entry will be created - :param pay_amount: amount of the payment to register, defaults to the residual of the invoice - :param date: payment date, defaults to fields.Date.context_today(self) - :param writeoff_acc: account in which to create a writeoff if pay_amount < self.residual, so that the invoice is fully paid - """ - if isinstance(pay_journal, pycompat.integer_types): - pay_journal = self.env['account.journal'].browse([pay_journal]) - assert len(self) == 1, "Can only pay one invoice at a time." + def _prepare_payment_vals(self, pay_journal, pay_amount=None, date=None, writeoff_acc=None, communication=None): payment_type = self.type in ('out_invoice', 'in_refund') and 'inbound' or 'outbound' if payment_type == 'inbound': payment_method = self.env.ref('account.account_payment_method_manual_in') @@ -1461,9 +1450,10 @@ class AccountInvoice(models.Model): payment_method = self.env.ref('account.account_payment_method_manual_out') journal_payment_methods = pay_journal.outbound_payment_method_ids - communication = self.type in ('in_invoice', 'in_refund') and self.reference or self.number - if self.origin: - communication = '%s (%s)' % (communication, self.origin) + if not communication: + communication = self.type in ('in_invoice', 'in_refund') and self.reference or self.number + if self.origin: + communication = '%s (%s)' % (communication, self.origin) payment_vals = { 'invoice_ids': [(6, 0, self.ids)], @@ -1478,7 +1468,22 @@ class AccountInvoice(models.Model): 'payment_difference_handling': writeoff_acc and 'reconcile' or 'open', 'writeoff_account_id': writeoff_acc and writeoff_acc.id or False, } + return payment_vals + @api.multi + def pay_and_reconcile(self, pay_journal, pay_amount=None, date=None, writeoff_acc=None): + """ Create and post an account.payment for the invoice self, which creates a journal entry that reconciles the invoice. + + :param pay_journal: journal in which the payment entry will be created + :param pay_amount: amount of the payment to register, defaults to the residual of the invoice + :param date: payment date, defaults to fields.Date.context_today(self) + :param writeoff_acc: account in which to create a writeoff if pay_amount < self.residual, so that the invoice is fully paid + """ + if isinstance(pay_journal, pycompat.integer_types): + pay_journal = self.env['account.journal'].browse([pay_journal]) + assert len(self) == 1, "Can only pay one invoice at a time." + + payment_vals = self._prepare_payment_vals(pay_journal, pay_amount=pay_amount, date=date, writeoff_acc=writeoff_acc) payment = self.env['account.payment'].create(payment_vals) payment.post() diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js index 765b7a9c6a5..e8a6838a139 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_model.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js @@ -793,7 +793,7 @@ var StatementModel = BasicModel.extend({ }).then(function (result) { if (result.length > 0) { var line = self.getLine(handle); - self.lines[handle].st_line.open_balance_account_id = line.amount < 0 ? result[0]['property_account_payable_id'][0] : result[0]['property_account_receivable_id'][0]; + self.lines[handle].st_line.open_balance_account_id = line.balance.amount < 0 ? result[0]['property_account_payable_id'][0] : result[0]['property_account_receivable_id'][0]; } }); }, @@ -853,6 +853,7 @@ var StatementModel = BasicModel.extend({ '__focus': false }); + prop.tax_exigible = tax.tax_exigibility === 'on_payment' ? true : undefined prop.amount = tax.base; prop.amount_str = field_utils.format.monetary(Math.abs(prop.amount), {}, formatOptions); prop.invalid = !self._isValid(prop); @@ -1204,6 +1205,7 @@ var StatementModel = BasicModel.extend({ name : prop.label, debit : amount > 0 ? amount : 0, credit : amount < 0 ? -amount : 0, + tax_exigible: prop.tax_exigible, analytic_tag_ids: [[6, null, _.pluck(prop.analytic_tag_ids, 'id')]] }; if (!isNaN(prop.id)) { @@ -1266,7 +1268,15 @@ var ManualModel = StatementModel.extend({ self.accounts = _.object(self.account_ids, _.pluck(accounts, 'code')); }); - return def_account.then(function () { + var def_reconcileModel = this._rpc({ + model: 'account.reconcile.model', + method: 'search_read', + }) + .then(function (reconcileModels) { + self.reconcileModels = reconcileModels; + }); + + return $.when(def_reconcileModel, def_account).then(function () { switch(context.mode) { case 'customers': case 'suppliers': @@ -1466,7 +1476,7 @@ var ManualModel = StatementModel.extend({ offset: 0, limitMoveLines: this.limitMoveLines, filter: "", - reconcileModels: [], + reconcileModels: this.reconcileModels, account_id: this._formatNameGet([data.account_id, data.account_name]), st_line: data, visible: true diff --git a/addons/account/static/tests/reconciliation_tests.js b/addons/account/static/tests/reconciliation_tests.js index 92cd9f47405..2e554a05776 100644 --- a/addons/account/static/tests/reconciliation_tests.js +++ b/addons/account/static/tests/reconciliation_tests.js @@ -721,6 +721,9 @@ QUnit.module('account', { }], payment_aml_ids: [], new_aml_dicts: []}]], "Should call process_bank_statement_line with ids"); + var def = $.Deferred(); + def.abort = function () {}; + event.data.callback(def); }); // click on reconcile button @@ -763,6 +766,9 @@ QUnit.module('account', { analytic_tag_ids: [[6, null, []]] }]}]], "Should call process_bank_statement_line with ids"); + var def = $.Deferred(); + def.abort = function () {}; + event.data.callback(def); }); // click on validate button @@ -816,6 +822,9 @@ QUnit.module('account', { name: 'SAJ/2014/002 and SAJ/2014/003 : Open balance' }]}]], "Should call process_bank_statement_line with ids"); + var def = $.Deferred(); + def.abort = function () {}; + event.data.callback(def); }); // click on validate button diff --git a/addons/account/views/account_unit_test.xml b/addons/account/views/account_unit_test.xml deleted file mode 100644 index 5e0828b25b6..00000000000 --- a/addons/account/views/account_unit_test.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - draft - in_invoice - - Test invoice 1 - - - Test Tax - 100.00 - - - - - Four Person Desk - - - 250 - 1 - - - - Large Meeting Table - - - 800 - 2 - - - - - € - - - - - - - - - - - open - - - - - - - - - paid - - - diff --git a/addons/base_address_extended/models/base_address_extended.py b/addons/base_address_extended/models/base_address_extended.py index 5d3ee07e342..766fac55a8b 100644 --- a/addons/base_address_extended/models/base_address_extended.py +++ b/addons/base_address_extended/models/base_address_extended.py @@ -33,6 +33,11 @@ class Partner(models.Model): street_number2 = fields.Char('Door', compute='_split_street', help="Door Number", inverse='_set_street', store=True) + @api.model + def _address_fields(self): + """Returns the list of address fields that are synced from the parent.""" + return super(Partner, self)._address_fields() + ['street_name', 'street_number', 'street_number2'] + def get_street_fields(self): """Returns the fields that can be used in a street format. Overwrite this function if you want to add your own fields.""" diff --git a/addons/base_geolocalize/models/res_partner.py b/addons/base_geolocalize/models/res_partner.py index 855b4d8bbd1..3594e1cb61b 100644 --- a/addons/base_geolocalize/models/res_partner.py +++ b/addons/base_geolocalize/models/res_partner.py @@ -1,23 +1,35 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. import json +import logging import requests from odoo import api, fields, models, tools, _ from odoo.exceptions import UserError +_logger = logging.getLogger(__name__) -def geo_find(addr): + +def geo_find(addr, apikey=False): if not addr: return None - url = 'https://maps.googleapis.com/maps/api/geocode/json' + + if not apikey: + raise UserError(_('''API key for GeoCoding (Places) required.\n + Save this key in System Parameters with key: google.api_key_geocode, value: + Visit https://developers.google.com/maps/documentation/geocoding/get-api-key for more information. + ''')) + + url = "https://maps.googleapis.com/maps/api/geocode/json" try: - result = requests.get(url, params={'sensor': 'false', 'address': addr}).json() + result = requests.get(url, params={'sensor': 'false', 'address': addr, 'key': apikey}).json() except Exception as e: raise UserError(_('Cannot contact geolocation servers. Please make sure that your Internet connection is up and running (%s).') % e) if result['status'] != 'OK': + if result.get('error_message'): + _logger.error(result['error_message']) return None try: @@ -45,22 +57,26 @@ class ResPartner(models.Model): partner_longitude = fields.Float(string='Geo Longitude', digits=(16, 5)) date_localization = fields.Date(string='Geolocation Date') + @classmethod + def _geo_localize(cls, apikey, street='', zip='', city='', state='', country=''): + search = geo_query_address(street=street, zip=zip, city=city, state=state, country=country) + result = geo_find(search, apikey) + if result is None: + search = geo_query_address(city=city, state=state, country=country) + result = geo_find(search, apikey) + return result + @api.multi def geo_localize(self): # We need country names in English below + apikey = self.env['ir.config_parameter'].sudo().get_param('google.api_key_geocode') for partner in self.with_context(lang='en_US'): - result = geo_find(geo_query_address(street=partner.street, - zip=partner.zip, - city=partner.city, - state=partner.state_id.name, - country=partner.country_id.name)) - if result is None: - result = geo_find(geo_query_address( - city=partner.city, - state=partner.state_id.name, - country=partner.country_id.name - )) - + result = partner._geo_localize(apikey, + partner.street, + partner.zip, + partner.city, + partner.state_id.name, + partner.country_id.name) if result: partner.write({ 'partner_latitude': result[0], diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py index 68d2e40123e..e6bad838235 100644 --- a/addons/calendar/models/calendar.py +++ b/addons/calendar/models/calendar.py @@ -634,6 +634,8 @@ class Meeting(models.Model): recurring_date = recurring_date.replace(tzinfo=None) else: recurring_date = todate(meeting.recurrent_id_date) + if date_field == "stop": + recurring_date += timedelta(hours=self.duration) rset1.exdate(recurring_date) invalidate = True return [d.astimezone(pytz.UTC) if d.tzinfo else d for d in rset1 if d.year < MAXYEAR] diff --git a/addons/calendar/tests/test_calendar_recurrent_event_case2.py b/addons/calendar/tests/test_calendar_recurrent_event_case2.py index 1f85dd83853..98b42f248b9 100644 --- a/addons/calendar/tests/test_calendar_recurrent_event_case2.py +++ b/addons/calendar/tests/test_calendar_recurrent_event_case2.py @@ -85,14 +85,23 @@ class TestRecurrentEvent(common.TransactionCase): for meeting in meetings: self.assertEqual(meeting.name, 'Sprint Review for google modules', 'Name not changed for id: %s' % meeting.id) + # I detach first occurrence to check it is not modified by changing recurrent event. + min(meetings, key=lambda m: m.start).detach_recurring_event() + # I change description of my weekly meeting Review code with programmer. idval = '%d-%s' % (self.calendar_event_sprint_review.id, '20110425124700') self.CalendarEvent.browse(idval).write({'description': 'Review code of the module: sync_google_calendar.'}) - # I check whether that all the records of this recurrence has been edited. - meetings = self.CalendarEvent.search([('recurrent_id', '=', self.calendar_event_sprint_review.id)]) - for meeting in meetings: - self.assertEqual(meeting.description, 'Review code of the module: sync_google_calendar.', 'Description not changed for id: %s' % meeting.id) + # I check that detached event has not been edited. + detached_meeting = self.CalendarEvent.search([('recurrent_id', '=', self.calendar_event_sprint_review.id)]) + self.assertEqual(detached_meeting.description, False, 'Detached event description changed for id: %s' % meeting.id) + + # I verify wether I find an event by date range when subsequent to a detached one. + last_meeting = max(meetings, key=lambda m: m.start) + meetings = self.CalendarEvent.with_context({'virtual_id': True}).search([ + ('start', '<=', last_meeting.stop), ('stop', '>=', last_meeting.start) + ]) + self.assertEqual(meetings.id, last_meeting.id, 'Last event should be found searching it by date range') # I update the description of two meetings, and check that both have been updated self.calendar_event_sprint_review.write({'description': "Some description"}) diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index d085d7635d6..bb032368eb9 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -935,7 +935,7 @@ class Lead(models.Model): email = '%s@%s' % (alias_record.alias_name, alias_record.alias_domain) email_link = "%s" % (email, email) sub_title = _('or send an email to %s') % (email_link) - return '

%s

%s

' % (help_title, sub_title) + return '

%s

%s

' % (help_title, sub_title) @api.multi def log_meeting(self, meeting_subject, meeting_date, duration): @@ -1126,7 +1126,7 @@ class Lead(models.Model): @api.multi def _notify_get_reply_to(self, default=None, records=None, company=None, doc_names=None): """ Override to set alias of lead and opportunities to their sales team if any. """ - aliases = self.mapped('team_id')._notify_get_reply_to(default=default, records=None, company=company, doc_names=None) + aliases = self.mapped('team_id').sudo()._notify_get_reply_to(default=default, records=None, company=company, doc_names=None) res = {lead.id: aliases.get(lead.team_id.id) for lead in self} leftover = self.filtered(lambda rec: not rec.team_id) if leftover: diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py index 1aab6729b8f..b4e26174807 100644 --- a/addons/hr_expense/models/hr_expense.py +++ b/addons/hr_expense/models/hr_expense.py @@ -147,14 +147,14 @@ class HrExpense(models.Model): @api.model def get_empty_list_help(self, help_message): - if help_message: + if help_message and "oe_view_nocontent_smiling_face" not in help_message: use_mailgateway = self.env['ir.config_parameter'].sudo().get_param('hr_expense.use_mailgateway') alias_record = use_mailgateway and self.env.ref('hr_expense.mail_alias_expense') or False if alias_record and alias_record.alias_domain and alias_record.alias_name: link = "%(email)s" % { 'email': '%s@%s' % (alias_record.alias_name, alias_record.alias_domain) } - return '

%s

%s

' % ( + return '

%s

%s

' % ( _('Add a new expense,'), _('or send receipts by email to %s.') % (link),) return super(HrExpense, self).get_empty_list_help(help_message) diff --git a/addons/l10n_co/models/res_partner.py b/addons/l10n_co/models/res_partner.py index f0c2ba19b28..986ecf2397e 100644 --- a/addons/l10n_co/models/res_partner.py +++ b/addons/l10n_co/models/res_partner.py @@ -46,7 +46,7 @@ class ResPartner(models.Model): except ValueError: partner.l10n_co_verification_code = False - @api.constrains('vat', 'commercial_partner_country_id', 'l10n_co_document_type') + @api.constrains('vat', 'country_id', 'l10n_co_document_type') def check_vat(self): # check_vat is implemented by base_vat which this localization # doesn't directly depend on. It is however automatically diff --git a/addons/mail/models/ir_actions.py b/addons/mail/models/ir_actions.py index 8c84144a12a..f6fa9d4fff0 100644 --- a/addons/mail/models/ir_actions.py +++ b/addons/mail/models/ir_actions.py @@ -80,7 +80,11 @@ class ServerActions(models.Model): # TDE CLEANME: when going to new api with server action, remove action if not action.template_id or not self._context.get('active_id'): return False - action.template_id.send_mail(self._context.get('active_id'), force_send=False, raise_exception=False) + # Clean context from default_type to avoid making attachment + # with wrong values in subsequent operations + cleaned_ctx = dict(self.env.context) + cleaned_ctx.pop('default_type', None) + action.template_id.with_context(cleaned_ctx).send_mail(self._context.get('active_id'), force_send=False, raise_exception=False) return False @api.model diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index a2c7c543f54..3273c2026eb 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -380,7 +380,9 @@ class MailThread(models.AbstractModel): 'email_link': email_link } } - return "%(static_help)s

%(dyn_help)s

" % { + # do not add alias two times if it was added previously + if "oe_view_nocontent_alias" not in help: + return "%(static_help)s

%(dyn_help)s

" % { 'static_help': help, 'dyn_help': _("Create a new %(document)s by sending an email to %(email_link)s") % { 'document': document_name, diff --git a/addons/mail/static/src/js/activity.js b/addons/mail/static/src/js/activity.js index 64af9f20a99..8c72ea3dc86 100644 --- a/addons/mail/static/src/js/activity.js +++ b/addons/mail/static/src/js/activity.js @@ -220,6 +220,28 @@ var BasicActivity = AbstractField.extend({ // Handlers //------------------------------------------------------------ + /** Binds a focusout handler on a bootstrap popover + * Useful to do some operations on the popover's HTML, + * like keeping the user's input for the feedback + * @param {JQuery} $popover_el: the element on which + * the popover() method has been called + */ + _bindPopoverFocusout: function ($popover_el) { + var self = this; + // Retrieve the actual popover's HTML + var $popover = $popover_el.data("bs.popover").tip(); + var activityID = $popover_el.data('activity-id'); + $popover.off('focusout'); + $popover.focusout(function (e) { + // outside click of popover hide the popover + // e.relatedTarget is the element receiving the focus + if (!$popover.is(e.relatedTarget) && !$popover.find(e.relatedTarget).length) { + self._draftFeedback[activityID] = $popover.find('#activity_feedback').val(); + $popover.popover('hide'); + } + }); + }, + /** * @private * @param {MouseEvent} ev @@ -284,15 +306,7 @@ var BasicActivity = AbstractField.extend({ $(".o_mail_activity_feedback.popover").not($popover).popover("hide"); $popover.addClass('o_mail_activity_feedback').attr('tabindex', 0); $popover.find('#activity_feedback').focus(); - $popover.off('focusout'); - $popover.focusout(function (e) { - // outside click of popover hide the popover - // e.relatedTarget is the element receiving the focus - if (!$popover.is(e.relatedTarget) && !$popover.find(e.relatedTarget).length) { - self._draftFeedback[activityID] = $popover.find('#activity_feedback').val(); - $popover.popover('hide'); - } - }); + self._bindPopoverFocusout($(this)); }).popover('show'); } }, diff --git a/addons/mail/static/tests/chatter_tests.js b/addons/mail/static/tests/chatter_tests.js index 4e1c782a715..144bba0f75c 100644 --- a/addons/mail/static/tests/chatter_tests.js +++ b/addons/mail/static/tests/chatter_tests.js @@ -13,6 +13,7 @@ var testUtils = require('web.test_utils'); var createAsyncView = testUtils.createAsyncView; var createView = testUtils.createView; +var Activity = require('mail.Activity'); var _t = core._t; QUnit.module('mail', {}, function () { @@ -82,6 +83,7 @@ QUnit.module('Chatter', { type: 'selection', selection: [['overdue', 'Overdue'], ['today', 'Today'], ['planned', 'Planned']], }, + note : { string: "Note", type: "char" }, }, }, 'mail.activity.type': { @@ -222,6 +224,74 @@ QUnit.test('basic rendering', function (assert) { form.destroy(); }); +QUnit.test('Activity Done keep feedback on blur', function (assert) { + assert.expect(3); + var done = assert.async(); + + this.data['mail.activity'].records = [ + {activity_type_id: 1, id: 1, user_id: 2, state: 'today', note: 'But I\'m talkin\' about Shaft'}, + ]; + this.data.partner.records[0].activity_ids = [1]; + + var shownDef = $.Deferred(); + var hiddenDef = $.Deferred(); + testUtils.patch(Activity, { + _bindPopoverFocusout: function () { + this._super.apply(this, arguments); + shownDef.resolve(); + }, + }); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + services: this.services, + res_id: 2, + arch:'
' + + '
' + + '' + + '
' + + '
', + }); + + // sanity checks + var $activityEl = form.$('.o_mail_activity[name=activity_ids]'); + assert.strictEqual($activityEl.find('.o_thread_message').length, 1, + 'There should be one activity'); + assert.strictEqual($activityEl.find('.o_thread_message .o_thread_message_note').text().trim(), + 'But I\'m talkin\' about Shaft', 'The activity should have the right note'); + + var $popoverEl = $activityEl.find('.o_thread_message_tools .o_activity_done'); + $popoverEl.on('hidden.bs.popover', hiddenDef.resolve.bind(hiddenDef)); + + // open popover + $popoverEl.click(); + + shownDef.then(function () { + // write a feedback and focusout + var $feedbackPopover = $popoverEl.data('bs.popover').tip(); + $feedbackPopover.find('#activity_feedback').val('John Shaft').focusout(); + + hiddenDef.then(function () { + shownDef = $.Deferred(); + + // re-open popover + $popoverEl.click(); + + shownDef.then(function () { + var $feedbackPopover = $popoverEl.data('bs.popover').tip(); + assert.strictEqual($feedbackPopover.find('#activity_feedback').val(), 'John Shaft', + "feedback should have been kept"); + + form.destroy(); + testUtils.unpatch(Activity); + done(); + }); + }); + }); +}); + QUnit.test('attachmentBox basic rendering', function (assert) { assert.expect(11); this.data.partner.records.push({ diff --git a/addons/membership/models/partner.py b/addons/membership/models/partner.py index dbb8abb5ae9..4ac0cedb4b0 100644 --- a/addons/membership/models/partner.py +++ b/addons/membership/models/partner.py @@ -164,8 +164,10 @@ class Partner(models.Model): @api.model def _cron_update_membership(self): - # used to recompute 'membership_state'; should no longer be necessary - pass + partners = self.search([('membership_state', 'in', ['invoiced', 'paid'])]) + # mark the field to be recomputed, and recompute it + partners._recompute_todo(self._fields['membership_state']) + self.recompute() @api.multi def create_membership_invoice(self, product_id=None, datas=None): diff --git a/addons/mrp/wizard/change_production_qty.py b/addons/mrp/wizard/change_production_qty.py index 453d551782c..31722c53618 100644 --- a/addons/mrp/wizard/change_production_qty.py +++ b/addons/mrp/wizard/change_production_qty.py @@ -90,6 +90,8 @@ class ChangeProductionQty(models.TransientModel): wo.qty_producing = quantity if wo.qty_produced < wo.qty_production and wo.state == 'done': wo.state = 'progress' + if wo.qty_produced == wo.qty_production and wo.state == 'progress': + wo.state = 'done' # assign moves; last operation receive all unassigned moves # TODO: following could be put in a function as it is similar as code in _workorders_create # TODO: only needed when creating new moves diff --git a/addons/payment/models/payment_acquirer.py b/addons/payment/models/payment_acquirer.py index 7f341a9d4ab..b3560e236f4 100644 --- a/addons/payment/models/payment_acquirer.py +++ b/addons/payment/models/payment_acquirer.py @@ -89,7 +89,6 @@ class PaymentAcquirer(models.Model): # Formerly associated to `authorize` option from auto_confirm capture_manually = fields.Boolean(string="Capture Amount Manually", help="Capture the amount from Odoo, when the delivery is completed.") - # Formerly associated to `generate_and_pay_invoice` option from auto_confirm journal_id = fields.Many2one( 'account.journal', 'Payment Journal', domain=[('type', 'in', ['bank', 'cash'])], help="""Payments will be registered into this journal. If you get paid straight on your bank account, @@ -197,9 +196,13 @@ class PaymentAcquirer(models.Model): def _check_required_if_provider(self): """ If the field has 'required_if_provider=""' attribute, then it required if record.provider is . """ + empty_field = [] for acquirer in self: - if any(getattr(f, 'required_if_provider', None) == acquirer.provider and not acquirer[k] for k, f in self._fields.items()): - return False + for k, f in acquirer._fields.items(): + if getattr(f, 'required_if_provider', None) == acquirer.provider and not acquirer[k]: + empty_field.append(self.env['ir.model.fields'].search([('name', '=', k), ('model', '=', acquirer._name)]).field_description) + if empty_field: + raise ValidationError((', ').join(empty_field)) return True _constraints = [ diff --git a/addons/point_of_sale/views/pos_config_view.xml b/addons/point_of_sale/views/pos_config_view.xml index b7de301c011..b36cd38f7dc 100644 --- a/addons/point_of_sale/views/pos_config_view.xml +++ b/addons/point_of_sale/views/pos_config_view.xml @@ -435,7 +435,7 @@ Operation type used to record product pickings
- +
diff --git a/addons/project/models/project.py b/addons/project/models/project.py index 24dd46b016c..47a20f62988 100644 --- a/addons/project/models/project.py +++ b/addons/project/models/project.py @@ -387,6 +387,7 @@ class Project(models.Model): action_context = safe_eval(action['context']) if action['context'] else {} action_context.update(self._context) action_context['search_default_parent_res_name'] = self.name + action_context.pop('group_by', None) return dict(action, context=action_context) # --------------------------------------------------- diff --git a/addons/rating/views/rating_view.xml b/addons/rating/views/rating_view.xml index aaca9062c6f..423c96b7e27 100644 --- a/addons/rating/views/rating_view.xml +++ b/addons/rating/views/rating_view.xml @@ -96,7 +96,7 @@ on - + diff --git a/addons/sale/views/sale_views.xml b/addons/sale/views/sale_views.xml index 416201d7dc1..828dda343ee 100644 --- a/addons/sale/views/sale_views.xml +++ b/addons/sale/views/sale_views.xml @@ -540,7 +540,7 @@ - + diff --git a/addons/sale_management/report/sale_report_templates.xml b/addons/sale_management/report/sale_report_templates.xml index 916c7a5042a..18ad9cf028b 100644 --- a/addons/sale_management/report/sale_report_templates.xml +++ b/addons/sale_management/report/sale_report_templates.xml @@ -3,6 +3,7 @@