diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index 90223d85923..4d7add6879a 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -3,7 +3,7 @@ from odoo import api, fields, models, _ from odoo.osv import expression from odoo.tools import float_is_zero, pycompat -from odoo.tools import float_compare, float_round +from odoo.tools import float_compare, float_round, float_repr from odoo.tools.misc import formatLang from odoo.exceptions import UserError, ValidationError @@ -606,7 +606,8 @@ class AccountBankStatementLine(models.Model): from_clause = "FROM account_move_line aml JOIN account_account acc ON acc.id = aml.account_id " where_clause = """WHERE aml.company_id = %(company_id)s AND ( - (aml.statement_id IS NULL AND aml.account_id IN %(account_payable_receivable)s) + (aml.statement_id IS NULL AND aml.account_id IN %(account_payable_receivable)s + AND aml.payment_id IS NOT NULL) OR ("""+acc_type+""" AND aml.reconciled = false) )""" @@ -630,7 +631,7 @@ class AccountBankStatementLine(models.Model): precision = st_line_currency and st_line_currency.decimal_places or company_currency.decimal_places params = {'company_id': self.env.user.company_id.id, 'account_payable_receivable': (self.journal_id.default_credit_account_id.id, self.journal_id.default_debit_account_id.id), - 'amount': float_round(amount, precision_digits=precision), + 'amount': float_repr(float_round(amount, precision_digits=precision), precision_digits=precision), 'partner_id': self.partner_id.id, 'excluded_ids': tuple(excluded_ids), 'ref': self.name, @@ -651,9 +652,9 @@ class AccountBankStatementLine(models.Model): # Look for a single move line with the same amount field = currency and 'amount_residual_currency' or 'amount_residual' liquidity_field = currency and 'amount_currency' or amount > 0 and 'debit' or 'credit' - liquidity_amt_clause = currency and '%(amount)s' or 'abs(%(amount)s)' + liquidity_amt_clause = currency and '%(amount)s::numeric' or 'abs(%(amount)s::numeric)' sql_query = self._get_common_sql_query(excluded_ids=excluded_ids) + \ - " AND ("+field+" = %(amount)s OR (acc.internal_type = 'liquidity' AND "+liquidity_field+" = " + liquidity_amt_clause + ")) \ + " AND ("+field+" = %(amount)s::numeric OR (acc.internal_type = 'liquidity' AND "+liquidity_field+" = " + liquidity_amt_clause + ")) \ ORDER BY date_maturity desc, aml.id desc LIMIT 1" self.env.cr.execute(sql_query, params) results = self.env.cr.fetchone() diff --git a/addons/account/models/account_config_settings.py b/addons/account/models/account_config_settings.py index 21f1b10eeb7..1e64aa013a4 100644 --- a/addons/account/models/account_config_settings.py +++ b/addons/account/models/account_config_settings.py @@ -127,7 +127,12 @@ class AccountConfigSettings(models.TransientModel): # related values, including the currency_id field on res_company. This in turn will trigger the recomputation # of account_move_line related field company_currency_id which can be slow depending on the number of entries # in the database. Thus, if we do not explicitely change the currency_id, we should not write it on the company + # Same for the field `code_digits` which will trigger a write on all the account.account to complete the + # code the missing characters to complete the desired number of digit, leading to a sql_constraint. if ('company_id' in values and 'currency_id' in values): - if self.env['res.company'].browse(values.get('company_id')).currency_id.id == values.get('currency_id'): + company = self.env['res.company'].browse(values.get('company_id')) + if company.currency_id.id == values.get('currency_id'): values.pop('currency_id') + if company.accounts_code_digits == values.get('code_digits'): + values.pop('code_digits') return super(AccountConfigSettings, self).create(values) diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 12c34e659b6..b84aa3513cc 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -489,7 +489,7 @@ class AccountInvoice(models.Model): @api.onchange('invoice_line_ids') def _onchange_invoice_line_ids(self): taxes_grouped = self.get_taxes_values() - tax_lines = self.tax_line_ids.browse([]) + tax_lines = self.tax_line_ids.filtered('manual') for tax in pycompat.values(taxes_grouped): tax_lines += tax_lines.new(tax) self.tax_line_ids = tax_lines diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index f50593e32cc..b7f90e49624 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -695,7 +695,7 @@ class AccountMoveLine(models.Model): company_currency = line.account_id.company_id.currency_id ret_line = { 'id': line.id, - 'name': line.name != '/' and line.move_id.name + ': ' + line.name or line.move_id.name, + 'name': line.name and line.name != '/' and line.move_id.name + ': ' + line.name or line.move_id.name, 'ref': line.move_id.ref or '', # For reconciliation between statement transactions and already registered payments (eg. checks) # NB : we don't use the 'reconciled' field because the line we're selecting is not the one that gets reconciled diff --git a/addons/account/models/partner.py b/addons/account/models/partner.py index 72976451e8c..f2f0f85f7c0 100644 --- a/addons/account/models/partner.py +++ b/addons/account/models/partner.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +from ast import literal_eval from operator import itemgetter import time @@ -427,3 +428,11 @@ class ResPartner(models.Model): return super(ResPartner, self)._commercial_fields() + \ ['debit_limit', 'property_account_payable_id', 'property_account_receivable_id', 'property_account_position_id', 'property_payment_term_id', 'property_supplier_payment_term_id', 'last_time_entries_checked'] + + @api.multi + def action_view_partner_invoices(self): + self.ensure_one() + action = self.env.ref('account.action_invoice_refund_out_tree').read()[0] + action['domain'] = literal_eval(action['domain']) + action['domain'].append(('partner_id', 'child_of', self.id)) + return action diff --git a/addons/account/views/account_invoice_view.xml b/addons/account/views/account_invoice_view.xml index cf1d70b40c0..fccaea99ecc 100644 --- a/addons/account/views/account_invoice_view.xml +++ b/addons/account/views/account_invoice_view.xml @@ -268,13 +268,13 @@ - + - + @@ -404,13 +404,13 @@ - + - + diff --git a/addons/account/views/account_view.xml b/addons/account/views/account_view.xml index 9757169d898..b71980e0ece 100644 --- a/addons/account/views/account_view.xml +++ b/addons/account/views/account_view.xml @@ -304,8 +304,8 @@
Keep empty for no control
- - + +
@@ -2037,8 +2037,8 @@ - - + + diff --git a/addons/account/views/partner_view.xml b/addons/account/views/partner_view.xml index bd2466bec6e..e1aa1d774c6 100644 --- a/addons/account/views/partner_view.xml +++ b/addons/account/views/partner_view.xml @@ -95,6 +95,17 @@ +
+ +
diff --git a/addons/account/views/product_view.xml b/addons/account/views/product_view.xml index f8979bd45da..8bbf5a69e0e 100644 --- a/addons/account/views/product_view.xml +++ b/addons/account/views/product_view.xml @@ -11,13 +11,13 @@ - + - + diff --git a/addons/account/views/report_invoice.xml b/addons/account/views/report_invoice.xml index 22906ebf10f..bea6aa94588 100644 --- a/addons/account/views/report_invoice.xml +++ b/addons/account/views/report_invoice.xml @@ -44,7 +44,7 @@ Customer Code:

-

+
Reference:

diff --git a/addons/account_voucher/views/account_voucher_views.xml b/addons/account_voucher/views/account_voucher_views.xml index ced7d16a4ef..ea279a24be2 100644 --- a/addons/account_voucher/views/account_voucher_views.xml +++ b/addons/account_voucher/views/account_voucher_views.xml @@ -253,7 +253,7 @@ - + @@ -369,7 +369,7 @@ - + diff --git a/addons/base_address_city/models/res_partner.py b/addons/base_address_city/models/res_partner.py index afa5cd759e8..f6fcbf61d42 100644 --- a/addons/base_address_city/models/res_partner.py +++ b/addons/base_address_city/models/res_partner.py @@ -20,6 +20,8 @@ class Partner(models.Model): @api.model def _fields_view_get_address(self, arch): arch = super(Partner, self)._fields_view_get_address(arch) + if not self._context.get('no_address_format'): + return arch # render the partner address accordingly to address_view_id doc = etree.fromstring(arch) for city_node in doc.xpath("//field[@name='city']"): diff --git a/addons/base_setup/views/res_partner_views.xml b/addons/base_setup/views/res_partner_views.xml index 9eef80f6539..2fd1658d825 100644 --- a/addons/base_setup/views/res_partner_views.xml +++ b/addons/base_setup/views/res_partner_views.xml @@ -8,7 +8,7 @@ - + diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index c3795afafe3..9043091164a 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -254,11 +254,13 @@ class Lead(models.Model): self.update(values) @api.constrains('user_id') + @api.multi def _valid_team(self): - if self.user_id: - values = self.with_context(team_id=self.team_id.id)._onchange_user_values(self.user_id.id) - if values: - self.update(values) + for lead in self: + if lead.user_id: + values = lead.with_context(team_id=lead.team_id.id)._onchange_user_values(lead.user_id.id) + if values: + lead.update(values) @api.onchange('state_id') def _onchange_state(self): diff --git a/addons/crm/views/crm_lead_views.xml b/addons/crm/views/crm_lead_views.xml index 4934fed5bc0..eff22f19721 100644 --- a/addons/crm/views/crm_lead_views.xml +++ b/addons/crm/views/crm_lead_views.xml @@ -94,7 +94,7 @@ - + @@ -243,7 +243,7 @@ - +
- +
@@ -486,7 +485,7 @@ - + diff --git a/addons/delivery/views/delivery_view.xml b/addons/delivery/views/delivery_view.xml index c85bf787320..d2e483bdddb 100644 --- a/addons/delivery/views/delivery_view.xml +++ b/addons/delivery/views/delivery_view.xml @@ -108,8 +108,8 @@ - - + + diff --git a/addons/fleet/views/fleet_vehicle_views.xml b/addons/fleet/views/fleet_vehicle_views.xml index 4a5416968d7..699d06a877b 100644 --- a/addons/fleet/views/fleet_vehicle_views.xml +++ b/addons/fleet/views/fleet_vehicle_views.xml @@ -66,7 +66,7 @@
@@ -180,7 +180,7 @@
- +
diff --git a/addons/google_calendar/static/src/js/google_calendar.js b/addons/google_calendar/static/src/js/google_calendar.js index 240b5568c0b..dbb7a308540 100644 --- a/addons/google_calendar/static/src/js/google_calendar.js +++ b/addons/google_calendar/static/src/js/google_calendar.js @@ -5,97 +5,143 @@ var core = require('web.core'); var Dialog = require('web.Dialog'); var framework = require('web.framework'); var CalendarRenderer = require('web.CalendarRenderer'); +var CalendarController = require('web.CalendarController'); var _t = core._t; -CalendarRenderer.include({ - events: _.extend({}, CalendarRenderer.prototype.events, { - 'click .o_google_sync_button': function () { - this.syncCalendar(this.fields_view); - } +CalendarController.include({ + custom_events: _.extend({}, CalendarController.prototype.custom_events, { + syncCalendar: '_onSyncCalendar', }), - syncCalendar: function() { + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * Try to sync the calendar with Google Calendar. According to the result + * from Google API, this function may require an action of the user by the + * mean of a dialog. + * + * @private + * @returns {OdooEvent} event + */ + _onSyncCalendar: function (event) { var self = this; var context = this.getSession().user_context; - this.$google_button.prop('disabled', true); - this.trigger_up('perform_rpc', { + this._rpc({ route: '/google_calendar/sync_data', - args: { - model: this.state.model, + params: { + model: this.modelName, fromurl: window.location.href, local_context: context, - }, - on_success: function (o) { - if (o.status === "need_auth") { - Dialog.alert(self, _t("You will be redirected to Google to authorize access to your calendar!"), { + } + }).then(function (o) { + if (o.status === "need_auth") { + Dialog.alert(self, _t("You will be redirected to Google to authorize access to your calendar!"), { + confirm_callback: function() { + framework.redirect(o.url); + }, + title: _t('Redirection'), + }); + } else if (o.status === "need_config_from_admin") { + if (!_.isUndefined(o.action) && parseInt(o.action)) { + Dialog.confirm(self, _t("The Google Synchronization needs to be configured before you can use it, do you want to do it now?"), { confirm_callback: function() { - framework.redirect(o.url); + self.do_action(o.action); }, - title: _t('Redirection'), + title: _t('Configuration'), }); - } else if (o.status === "need_config_from_admin") { - if (!_.isUndefined(o.action) && parseInt(o.action)) { - Dialog.confirm(self, _t("The Google Synchronization needs to be configured before you can use it, do you want to do it now?"), { - confirm_callback: function() { - self.do_action(o.action); - }, - title: _t('Configuration'), - }); - } else { - Dialog.alert(self, _t("An administrator needs to configure Google Synchronization before you can use it!"), { - title: _t('Configuration'), - }); - } - } else if (o.status === "need_refresh") { - self.$calendar.fullCalendar('refetchEvents'); - } else if (o.status === "need_reset") { - var confirm_text1 = _t("The account you are trying to synchronize (%s) is not the same as the last one used (%s)!"); - var confirm_text2 = _t("In order to do this, you first need to disconnect all existing events from the old account."); - var confirm_text3 = _t("Do you want to do this now?"); - var text = _.str.sprintf(confirm_text1 + "\n" + confirm_text2 + "\n\n" + confirm_text3, o.info.new_name, o.info.old_name); - Dialog.confirm(self, text, { - confirm_callback: function() { - self._rpc({ - route: '/google_calendar/remove_references', - params: { - model: self.state.model, - local_context: context, - }, - }) - .done(function(o) { - if (o.status === "OK") { - Dialog.alert(self, _t("All events have been disconnected from your previous account. You can now restart the synchronization"), { - title: _t('Event disconnection success'), - }); - } else if (o.status === "KO") { - Dialog.alert(self, _t("An error occured while disconnecting events from your previous account. Please retry or contact your administrator."), { - title: _t('Event disconnection error'), - }); - } // else NOP - }); - }, - title: _t('Accounts'), + } else { + Dialog.alert(self, _t("An administrator needs to configure Google Synchronization before you can use it!"), { + title: _t('Configuration'), }); } - - self.$google_button.prop('disabled', false); - }, + } else if (o.status === "need_refresh") { + self.reload(); + } else if (o.status === "need_reset") { + var confirmText1 = _t("The account you are trying to synchronize (%s) is not the same as the last one used (%s)!"); + var confirmText2 = _t("In order to do this, you first need to disconnect all existing events from the old account."); + var confirmText3 = _t("Do you want to do this now?"); + var text = _.str.sprintf(confirmText1 + "\n" + confirmText2 + "\n\n" + confirmText3, o.info.new_name, o.info.old_name); + Dialog.confirm(self, text, { + confirm_callback: function() { + self._rpc({ + route: '/google_calendar/remove_references', + params: { + model: self.state.model, + local_context: context, + }, + }) + .done(function(o) { + if (o.status === "OK") { + Dialog.alert(self, _t("All events have been disconnected from your previous account. You can now restart the synchronization"), { + title: _t('Event disconnection success'), + }); + } else if (o.status === "KO") { + Dialog.alert(self, _t("An error occured while disconnecting events from your previous account. Please retry or contact your administrator."), { + title: _t('Event disconnection error'), + }); + } // else NOP + }); + }, + title: _t('Accounts'), + }); + } + }).always(function () { + event.data.on_always(); }); - }, - initSidebar: function() { + } +}); + +CalendarRenderer.include({ + events: _.extend({}, CalendarRenderer.prototype.events, { + 'click .o_google_sync_button': '_onSyncCalendar', + }), + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Adds the Sync with Google button in the sidebar + * + * @private + */ + _initSidebar: function () { var self = this; - this._super(); - this.$google_button = $(); - if (this.state.model === "calendar.event") { - this.$google_button = $('
- + @@ -175,7 +175,7 @@
- +
diff --git a/addons/hr_expense/views/hr_expense_views.xml b/addons/hr_expense/views/hr_expense_views.xml index f0374c92a05..cc9d9fd1c23 100644 --- a/addons/hr_expense/views/hr_expense_views.xml +++ b/addons/hr_expense/views/hr_expense_views.xml @@ -299,7 +299,7 @@ - + @@ -464,7 +464,7 @@
@@ -255,7 +255,6 @@ - @@ -283,7 +282,7 @@
- + diff --git a/addons/hr_timesheet/models/project.py b/addons/hr_timesheet/models/project.py index a2b7a9c25e4..0fc7aa314bf 100644 --- a/addons/hr_timesheet/models/project.py +++ b/addons/hr_timesheet/models/project.py @@ -48,3 +48,13 @@ class Task(models.Model): timesheet_ids = fields.One2many('account.analytic.line', 'task_id', 'Timesheets') _constraints = [(models.BaseModel._check_recursion, 'Circular references are not permitted between tasks and sub-tasks', ['parent_id'])] + + + @api.model + def create(self, vals): + context = dict(self.env.context) + # Remove default_parent_id to avoid a confusion in get_record_data + if context.get('default_parent_id', False): + vals['parent_id'] = context.pop('default_parent_id', None) + task = super(Task, self.with_context(context)).create(vals) + return task \ No newline at end of file diff --git a/addons/l10n_fr_certification/__manifest__.py b/addons/l10n_fr_certification/__manifest__.py index e83e9102914..7e2ea327d54 100644 --- a/addons/l10n_fr_certification/__manifest__.py +++ b/addons/l10n_fr_certification/__manifest__.py @@ -9,7 +9,7 @@ """, 'depends': ['l10n_fr'], 'installable': True, - 'auto_install': True, + 'auto_install': False, 'application': True, 'data': [ 'views/no_cancel.xml', diff --git a/addons/l10n_fr_certification/models/account.py b/addons/l10n_fr_certification/models/account.py index ecb0f5aff8b..bef0880d7ca 100644 --- a/addons/l10n_fr_certification/models/account.py +++ b/addons/l10n_fr_certification/models/account.py @@ -102,7 +102,7 @@ class AccountMove(models.Model): """ moves = self.search([('state', '=', 'posted'), ('company_id', '=', company_id), - ('l10n_fr_secure_sequence_number', '!=', False)], + ('l10n_fr_secure_sequence_number', '!=', 0)], order="l10n_fr_secure_sequence_number ASC") if not moves: diff --git a/addons/mail/static/src/js/client_action.js b/addons/mail/static/src/js/client_action.js index 5fd383fee18..691d1ee968d 100644 --- a/addons/mail/static/src/js/client_action.js +++ b/addons/mail/static/src/js/client_action.js @@ -221,7 +221,9 @@ var ChatAction = Widget.extend(ControlPanelMixin, { this.$buttons.on('click', '.o_mail_chat_button_mark_read', function () { chat_manager.mark_all_as_read(self.channel, self.domain); }); - this.$buttons.on('click', '.o_mail_chat_button_unstar_all', chat_manager.unstar_all); + this.$buttons.on('click', '.o_mail_chat_button_unstar_all', function () { + chat_manager.unstar_all(); + }); this.thread.on('redirect', this, function (res_model, res_id) { chat_manager.redirect(res_model, res_id, this.set_channel.bind(this)); diff --git a/addons/maintenance/views/maintenance_views.xml b/addons/maintenance/views/maintenance_views.xml index 4bb973aad1c..b66903adabf 100644 --- a/addons/maintenance/views/maintenance_views.xml +++ b/addons/maintenance/views/maintenance_views.xml @@ -710,7 +710,7 @@
- + diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py index 37d40718516..7142cb5967c 100644 --- a/addons/mass_mailing/models/mass_mailing.py +++ b/addons/mass_mailing/models/mass_mailing.py @@ -434,15 +434,14 @@ class MassMailing(models.Model): def _compute_next_departure(self): cron_next_call = self.env.ref('mass_mailing.ir_cron_mass_mailing_queue').sudo().nextcall + str2dt = fields.Datetime.from_string + cron_time = str2dt(cron_next_call) for mass_mailing in self: - schedule_date = mass_mailing.schedule_date - if schedule_date: - if datetime.now() > fields.Datetime.from_string(schedule_date): - mass_mailing.next_departure = cron_next_call - else: - mass_mailing.next_departure = schedule_date + if mass_mailing.schedule_date: + schedule_date = str2dt(mass_mailing.schedule_date) + mass_mailing.next_departure = max(schedule_date, cron_time) else: - mass_mailing.next_departure = cron_next_call + mass_mailing.next_departure = cron_time @api.onchange('mass_mailing_campaign_id') def _onchange_mass_mailing_campaign_id(self): diff --git a/addons/mass_mailing/views/mass_mailing_views.xml b/addons/mass_mailing/views/mass_mailing_views.xml index 17d9818077c..d9c07aff354 100644 --- a/addons/mass_mailing/views/mass_mailing_views.xml +++ b/addons/mass_mailing/views/mass_mailing_views.xml @@ -423,7 +423,7 @@ context="{'search_default_not_opt_out':1}"/>
@@ -758,7 +758,7 @@ - + @@ -817,11 +817,11 @@
+

-

Mailings

diff --git a/addons/membership/views/product_views.xml b/addons/membership/views/product_views.xml index 909ed782082..988938a1d8d 100644 --- a/addons/membership/views/product_views.xml +++ b/addons/membership/views/product_views.xml @@ -98,7 +98,7 @@ - +
- + @@ -156,7 +156,7 @@ - + diff --git a/addons/note/views/note_views.xml b/addons/note/views/note_views.xml index 83f658acd06..0373dd330a5 100644 --- a/addons/note/views/note_views.xml +++ b/addons/note/views/note_views.xml @@ -87,7 +87,6 @@ - @@ -95,7 +94,7 @@
- +