diff --git a/addons/account/data/mail_template_data.xml b/addons/account/data/mail_template_data.xml index f461a271452..4bb340a4f5c 100644 --- a/addons/account/data/mail_template_data.xml +++ b/addons/account/data/mail_template_data.xml @@ -7,7 +7,7 @@ Invoicing: Invoice email - ${(object.user_id.email and '%s <%s>' % (object.user_id.name, object.user_id.email) or '')|safe} + ${(object.user_id.email and '"%s" <%s>' % (object.user_id.name, object.user_id.email) or '')|safe} ${object.company_id.name} Invoice (Ref ${object.number or 'n/a'}) ${object.partner_id.id} diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 905f10f4334..020d725e3ca 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -1374,8 +1374,6 @@ class AccountInvoice(models.Model): else: payment_method = self.env.ref('account.account_payment_method_manual_out') journal_payment_methods = pay_journal.outbound_payment_method_ids - if payment_method not in journal_payment_methods: - raise UserError(_('No appropriate payment method enabled on journal %s') % pay_journal.name) communication = self.type in ('in_invoice', 'in_refund') and self.reference or self.number if self.origin: diff --git a/addons/account/models/company.py b/addons/account/models/company.py index 8ae4230a7b2..0209ea5c849 100644 --- a/addons/account/models/company.py +++ b/addons/account/models/company.py @@ -2,10 +2,12 @@ from datetime import timedelta, datetime import calendar +import time +from dateutil.relativedelta import relativedelta from odoo import fields, models, api, _ from odoo.exceptions import ValidationError, UserError -from odoo.exceptions import UserError +from odoo.tools.misc import DEFAULT_SERVER_DATE_FORMAT from odoo.tools.float_utils import float_round, float_is_zero @@ -63,6 +65,61 @@ Best Regards,''')) account_setup_coa_done = fields.Boolean(string='Chart of Account Checked', help="Technical field holding the status of the chart of account setup step.") account_setup_bar_closed = fields.Boolean(string='Setup Bar Closed', help="Technical field set to True when setup bar has been closed by the user.") + @api.multi + def _check_lock_dates(self, vals): + '''Check the lock dates for the current companies. This can't be done in a api.constrains because we need + to perform some comparison between new/old values. This method forces the lock dates to be irreversible. + + * You cannot define stricter conditions on advisors than on users. Then, the lock date on advisor must be set + after the lock date for users. + * You cannot lock a period that is not finished yet. Then, the lock date for advisors must be set after the + last day of the previous month. + * The new lock date for advisors must be set after the previous lock date. + + :param vals: The values passed to the write method. + ''' + period_lock_date = vals.get('period_lock_date') and\ + time.strptime(vals['period_lock_date'], DEFAULT_SERVER_DATE_FORMAT) + fiscalyear_lock_date = vals.get('fiscalyear_lock_date') and\ + time.strptime(vals['fiscalyear_lock_date'], DEFAULT_SERVER_DATE_FORMAT) + + previous_month = datetime.strptime(fields.Date.today(), DEFAULT_SERVER_DATE_FORMAT) + relativedelta(months=-1) + days_previous_month = calendar.monthrange(previous_month.year, previous_month.month) + previous_month = previous_month.replace(day=days_previous_month[1]).timetuple() + for company in self: + old_fiscalyear_lock_date = company.fiscalyear_lock_date and\ + time.strptime(company.fiscalyear_lock_date, DEFAULT_SERVER_DATE_FORMAT) + + # The user attempts to remove the lock date for advisors + if old_fiscalyear_lock_date and not fiscalyear_lock_date and 'fiscalyear_lock_date' in vals: + raise ValidationError(_('The lock date for advisors is irreversible and can\'t be removed.')) + + # The user attempts to set a lock date for advisors prior to the previous one + if old_fiscalyear_lock_date and fiscalyear_lock_date and fiscalyear_lock_date < old_fiscalyear_lock_date: + raise ValidationError(_('The new lock date for advisors must be set after the previous lock date.')) + + # In case of no new fiscal year in vals, fallback to the oldest + if not fiscalyear_lock_date: + if old_fiscalyear_lock_date: + fiscalyear_lock_date = old_fiscalyear_lock_date + else: + continue + + # The user attempts to set a lock date for advisors prior to the last day of previous month + if fiscalyear_lock_date > previous_month: + raise ValidationError(_('You cannot lock a period that is not finished yet. Please make sure that the lock date for advisors is not set after the last day of the previous month.')) + + # In case of no new period lock date in vals, fallback to the one defined in the company + if not period_lock_date: + if company.period_lock_date: + period_lock_date = time.strptime(company.period_lock_date, DEFAULT_SERVER_DATE_FORMAT) + else: + continue + + # The user attempts to set a lock date for advisors prior to the lock date for users + if period_lock_date < fiscalyear_lock_date: + raise ValidationError(_('You cannot define stricter conditions on advisors than on users. Please make sure that the lock date on advisor is set before the lock date for users.')) + @api.model def _verify_fiscalyear_last_day(self, company_id, last_day, last_month): company = self.browse(company_id) diff --git a/addons/account/tests/test_account_move_closed_period.py b/addons/account/tests/test_account_move_closed_period.py index 298738a0d6c..5de3e97ac4f 100644 --- a/addons/account/tests/test_account_move_closed_period.py +++ b/addons/account/tests/test_account_move_closed_period.py @@ -1,6 +1,8 @@ from odoo.addons.account.tests.account_test_classes import AccountingTestCase from odoo.osv.orm import except_orm -from datetime import datetime, timedelta +from datetime import datetime +from dateutil.relativedelta import relativedelta +from calendar import monthrange from odoo.tools import DEFAULT_SERVER_DATE_FORMAT from odoo.tests import tagged @@ -14,14 +16,16 @@ class TestPeriodState(AccountingTestCase): def setUp(self): super(TestPeriodState, self).setUp() self.user_id = self.env.user - self.day_before_yesterday = datetime.now() - timedelta(2) - self.yesterday = datetime.now() - timedelta(1) - self.yesterday_str = self.yesterday.strftime(DEFAULT_SERVER_DATE_FORMAT) + + last_day_month = datetime.now() - relativedelta(months=1) + last_day_month = last_day_month.replace(day=monthrange(last_day_month.year, last_day_month.month)[1]) + self.last_day_month_str = last_day_month.strftime(DEFAULT_SERVER_DATE_FORMAT) + #make sure there is no unposted entry - draft_entries = self.env['account.move'].search([('date', '<=', self.yesterday_str), ('state', '=', 'draft')]) + draft_entries = self.env['account.move'].search([('date', '<=', self.last_day_month_str), ('state', '=', 'draft')]) if draft_entries: draft_entries.post() - self.user_id.company_id.write({'fiscalyear_lock_date': self.yesterday_str}) + self.user_id.company_id.fiscalyear_lock_date = self.last_day_month_str self.sale_journal_id = self.env['account.journal'].search([('type', '=', 'sale')])[0] self.account_id = self.env['account.account'].search([('internal_type', '=', 'receivable')])[0] @@ -30,7 +34,7 @@ class TestPeriodState(AccountingTestCase): move = self.env['account.move'].create({ 'name': '/', 'journal_id': self.sale_journal_id.id, - 'date': self.day_before_yesterday.strftime(DEFAULT_SERVER_DATE_FORMAT), + 'date': self.last_day_month_str, 'line_ids': [(0, 0, { 'name': 'foo', 'debit': 10, diff --git a/addons/account/tests/test_reconciliation.py b/addons/account/tests/test_reconciliation.py index 4a50c812620..1fce897b602 100644 --- a/addons/account/tests/test_reconciliation.py +++ b/addons/account/tests/test_reconciliation.py @@ -42,6 +42,12 @@ class TestReconciliation(AccountingTestCase): self.diff_income_account = self.env['res.users'].browse(self.env.uid).company_id.income_currency_exchange_account_id self.diff_expense_account = self.env['res.users'].browse(self.env.uid).company_id.expense_currency_exchange_account_id + self.inbound_payment_method = self.env['account.payment.method'].create({ + 'name': 'inbound', + 'code': 'IN', + 'payment_type': 'inbound', + }) + def create_invoice(self, type='out_invoice', invoice_amount=50, currency_id=None): #we create an invoice in given currency invoice = self.account_invoice_model.create({'partner_id': self.partner_agrolait_id, @@ -709,6 +715,49 @@ class TestReconciliation(AccountingTestCase): credit_aml.with_context(invoice_id=inv.id).remove_move_reconcile() self.assertAlmostEquals(inv.residual, 111) + def test_revert_payment_and_reconcile(self): + payment = self.env['account.payment'].create({ + 'payment_method_id': self.inbound_payment_method.id, + 'payment_type': 'inbound', + 'partner_type': 'customer', + 'partner_id': self.partner_agrolait_id, + 'journal_id': self.bank_journal_usd.id, + 'payment_date': '2018-06-04', + 'amount': 666, + }) + payment.post() + + self.assertEqual(len(payment.move_line_ids), 2) + + bank_line = payment.move_line_ids.filtered(lambda l: l.account_id.id == self.bank_journal_usd.default_debit_account_id.id) + customer_line = payment.move_line_ids - bank_line + + self.assertEqual(len(bank_line), 1) + self.assertEqual(len(customer_line), 1) + self.assertNotEqual(bank_line.id, customer_line.id) + + self.assertEqual(bank_line.move_id.id, customer_line.move_id.id) + move = bank_line.move_id + + # Reversing the payment's move + reversed_move_list = move.reverse_moves('2018-06-04') + self.assertEqual(len(reversed_move_list), 1) + reversed_move = self.env['account.move'].browse(reversed_move_list[0]) + + self.assertEqual(len(reversed_move.line_ids), 2) + + # Testing the reconciliation matching between the move lines and their reversed counterparts + reversed_bank_line = reversed_move.line_ids.filtered(lambda l: l.account_id.id == self.bank_journal_usd.default_debit_account_id.id) + reversed_customer_line = reversed_move.line_ids - reversed_bank_line + + self.assertEqual(len(reversed_bank_line), 1) + self.assertEqual(len(reversed_customer_line), 1) + self.assertNotEqual(reversed_bank_line.id, reversed_customer_line.id) + self.assertEqual(reversed_bank_line.move_id.id, reversed_customer_line.move_id.id) + + self.assertEqual(reversed_bank_line.full_reconcile_id.id, bank_line.full_reconcile_id.id) + self.assertEqual(reversed_customer_line.full_reconcile_id.id, customer_line.full_reconcile_id.id) + def test_partial_reconcile_currencies_02(self): #### # Day 1: Invoice Cust/001 to customer (expressed in USD) diff --git a/addons/account/views/account_view.xml b/addons/account/views/account_view.xml index 00b716edb51..503ac0bd0a7 100644 --- a/addons/account/views/account_view.xml +++ b/addons/account/views/account_view.xml @@ -1166,7 +1166,8 @@ + domain="['|', ('parent_id', '=', False), ('is_company', '=', True)]" + attrs="{'readonly': [('parent_state', '=', 'posted')]}"/> diff --git a/addons/account_lock/__init__.py b/addons/account_lock/__init__.py new file mode 100644 index 00000000000..cde864bae21 --- /dev/null +++ b/addons/account_lock/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import models diff --git a/addons/account_lock/__manifest__.py b/addons/account_lock/__manifest__.py new file mode 100644 index 00000000000..67e76b49098 --- /dev/null +++ b/addons/account_lock/__manifest__.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +{ + 'name' : 'Irreversible Lock Date', + 'version' : '1.0', + 'category': 'Accounting', + 'description': """ + Make the lock date irreversible: + + * You cannot define stricter conditions on advisors than on users. Then, the lock date on advisor must be set before the lock date for users. + * You cannot lock a period that is not finished yet. Then, the lock date for advisors must be set before the last day of the previous month. + * The new lock date for advisors must be set after the previous lock date. + """, + 'depends' : ['account'], + 'data': [], +} diff --git a/addons/account_lock/models/__init__.py b/addons/account_lock/models/__init__.py new file mode 100644 index 00000000000..e759f74fc8d --- /dev/null +++ b/addons/account_lock/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import res_company diff --git a/addons/account_lock/models/res_company.py b/addons/account_lock/models/res_company.py new file mode 100644 index 00000000000..5a01046f36b --- /dev/null +++ b/addons/account_lock/models/res_company.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- + +from odoo import models, api + + +class ResCompany(models.Model): + _inherit = 'res.company' + + @api.multi + def write(self, vals): + # fiscalyear_lock_date can't be set to a prior date + if 'fiscalyear_lock_date' in vals or 'period_lock_date' in vals: + self._check_lock_dates(vals) + return super(ResCompany, self).write(vals) diff --git a/addons/analytic/models/analytic_account.py b/addons/analytic/models/analytic_account.py index 9cd801144e8..7096852528c 100644 --- a/addons/analytic/models/analytic_account.py +++ b/addons/analytic/models/analytic_account.py @@ -97,6 +97,7 @@ class AccountAnalyticAccount(models.Model): domain=domain + [('amount', '>=', 0.0)], fields=['account_id', 'currency_id', 'amount'], groupby=['account_id', 'currency_id'], + lazy=False, ) data_credit = defaultdict(float) for l in credit_groups: @@ -106,6 +107,7 @@ class AccountAnalyticAccount(models.Model): domain=domain + [('amount', '<', 0.0)], fields=['account_id', 'currency_id', 'amount'], groupby=['account_id', 'currency_id'], + lazy=False, ) data_debit = defaultdict(float) for l in debit_groups: diff --git a/addons/barcodes/static/src/js/barcode_events.js b/addons/barcodes/static/src/js/barcode_events.js index a2e5808f8be..2d602a36b97 100644 --- a/addons/barcodes/static/src/js/barcode_events.js +++ b/addons/barcodes/static/src/js/barcode_events.js @@ -64,12 +64,14 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { 'position': 'fixed', 'top': '50%', 'transform': 'translateY(-50%)', - 'opacity': 0, + 'z-index': '-1', }, }); + // Avoid to show autocomplete for a non appearing input + this.$barcodeInput.attr('autocomplete', 'off'); } - this.__removeBarcodeField = _.debounce(this._removeBarcodeField, this.inputTimeOut); + this.__blurBarcodeInput = _.debounce(this._blurBarcodeInput, this.inputTimeOut); }, handle_buffered_keys: function() { @@ -232,7 +234,7 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { this.max_time_between_keys_in_ms); } // if the barcode input doesn't receive keydown for a while, remove it. - this.__removeBarcodeField(); + this.__blurBarcodeInput(); } }, @@ -247,21 +249,22 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { var barcodeValue = this.$barcodeInput.val(); if (barcodeValue.match(this.regexp)) { core.bus.trigger('barcode_scanned', barcodeValue, $(e.target).parent()[0]); - this.$barcodeInput.val(''); + this._blurBarcodeInput(); } }, /** - * Remove the temporary input created to store the barcode value. - * If nothing happens, this input will be removed, so the focus will be lost - * and the virtual keyboard on mobile devices will be closed. + * Removes the value and focus from the barcode input. + * If nothing happens, the focus will be lost and + * the virtual keyboard on mobile devices will be closed. * * @private */ - _removeBarcodeField: function () { + _blurBarcodeInput: function () { if (this.$barcodeInput) { - // Reset the value and remove from the DOM. - this.$barcodeInput.val('').remove(); + // Close the virtual keyboard on mobile browsers + // FIXME: actually we can't prevent keyboard from opening + this.$barcodeInput.val('').blur(); } }, diff --git a/addons/base_automation/i18n/tr.po b/addons/base_automation/i18n/tr.po index f943fc6fda0..f1bdb579a5d 100644 --- a/addons/base_automation/i18n/tr.po +++ b/addons/base_automation/i18n/tr.po @@ -123,7 +123,7 @@ msgstr "Bağlantı Modeli" #. module: base_automation #: model:ir.model.fields,field_description:base_automation.field_base_automation__binding_type msgid "Binding Type" -msgstr "" +msgstr "Bağlantı Türü" #. module: base_automation #: selection:base.automation.lead.test,state:0 diff --git a/addons/base_geolocalize/i18n/tr.po b/addons/base_geolocalize/i18n/tr.po index b858540b3b8..506cb5037a7 100644 --- a/addons/base_geolocalize/i18n/tr.po +++ b/addons/base_geolocalize/i18n/tr.po @@ -3,19 +3,19 @@ # * base_geolocalize # # Translators: -# Martin Trigaux , 2017 +# Martin Trigaux, 2017 # Ahmet Altinisik , 2017 # Ayhan KIZILTAN , 2017 # gezgin biri , 2017 -# Murat Kaplan , 2017 # Matanat Ahmadova , 2017 +# Umur Akın , 2018 msgid "" msgstr "" -"Project-Id-Version: Odoo Server 10.saas~18\n" +"Project-Id-Version: Odoo Server saas~11.2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-09-20 09:53+0000\n" -"PO-Revision-Date: 2017-09-20 09:53+0000\n" -"Last-Translator: Matanat Ahmadova , 2017\n" +"POT-Creation-Date: 2018-03-22 14:10+0000\n" +"PO-Revision-Date: 2018-03-22 14:10+0000\n" +"Last-Translator: Umur Akın , 2018\n" "Language-Team: Turkish (https://www.transifex.com/odoo/teams/41243/tr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -45,6 +45,8 @@ msgid "" "Cannot contact geolocation servers. Please make sure that your Internet " "connection is up and running (%s)." msgstr "" +"Coğrafi konumlandıma sunucusuna bağlanılamıyor. Lütfen internet bağlantınızı" +" açık ve çalışmakta olduğundan emin olun (%s)." #. module: base_geolocalize #: model:ir.model,name:base_geolocalize.model_res_partner @@ -52,21 +54,21 @@ msgid "Contact" msgstr "Kontak" #. module: base_geolocalize -#: model:ir.model.fields,field_description:base_geolocalize.field_res_partner_partner_latitude -#: model:ir.model.fields,field_description:base_geolocalize.field_res_users_partner_latitude +#: model:ir.model.fields,field_description:base_geolocalize.field_res_partner__partner_latitude +#: model:ir.model.fields,field_description:base_geolocalize.field_res_users__partner_latitude msgid "Geo Latitude" msgstr "Coğrafi Enlem" #. module: base_geolocalize -#: model:ir.model.fields,field_description:base_geolocalize.field_res_partner_partner_longitude -#: model:ir.model.fields,field_description:base_geolocalize.field_res_users_partner_longitude +#: model:ir.model.fields,field_description:base_geolocalize.field_res_partner__partner_longitude +#: model:ir.model.fields,field_description:base_geolocalize.field_res_users__partner_longitude msgid "Geo Longitude" msgstr "Coğ Boylam" #. module: base_geolocalize #: model:ir.ui.view,arch_db:base_geolocalize.view_crm_partner_geo_form msgid "Geolocate" -msgstr "" +msgstr "Yer Etiketi" #. module: base_geolocalize #: model:ir.ui.view,arch_db:base_geolocalize.view_crm_partner_geo_form @@ -74,8 +76,8 @@ msgid "Geolocation" msgstr "Coğrafi Konum" #. module: base_geolocalize -#: model:ir.model.fields,field_description:base_geolocalize.field_res_partner_date_localization -#: model:ir.model.fields,field_description:base_geolocalize.field_res_users_date_localization +#: model:ir.model.fields,field_description:base_geolocalize.field_res_partner__date_localization +#: model:ir.model.fields,field_description:base_geolocalize.field_res_users__date_localization msgid "Geolocation Date" msgstr "Coğrafi Konum" diff --git a/addons/base_import/i18n/zh_CN.po b/addons/base_import/i18n/zh_CN.po index 0eb1389d7a7..222ed8f1b4a 100644 --- a/addons/base_import/i18n/zh_CN.po +++ b/addons/base_import/i18n/zh_CN.po @@ -8,13 +8,14 @@ # 苏州远鼎 , 2016 # liAnGjiA , 2016 # 山西清水欧度(QQ:54773801) <54773801@qq.com>, 2018 +# John Lin , 2018 msgid "" msgstr "" "Project-Id-Version: Odoo Server saas~11.2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-03-22 14:10+0000\n" "PO-Revision-Date: 2018-03-22 14:10+0000\n" -"Last-Translator: 山西清水欧度(QQ:54773801) <54773801@qq.com>, 2018\n" +"Last-Translator: John Lin , 2018\n" "Language-Team: Chinese (China) (https://www.transifex.com/odoo/teams/41243/zh_CN/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -143,7 +144,7 @@ msgstr "十进制分隔符:" #: code:addons/base_import/static/src/xml/base_import.xml:76 #, python-format msgid "Defer parent/child computation" -msgstr "" +msgstr "推迟父/子计算" #. module: base_import #: model:ir.model.fields,field_description:base_import.field_base_import_import__display_name @@ -317,7 +318,7 @@ msgid "" "If the model uses parent/child relations, computing the" " parent / child relation occurs on every line, and lead" " to a slower import. Defering it can speed up import." -msgstr "" +msgstr "如果该模型使用父/子关系,每行都会发生父/子关系计算,并使导入更慢。推迟她能提升导入速度。" #. module: base_import #. openerp-web diff --git a/addons/calendar/i18n/calendar.pot b/addons/calendar/i18n/calendar.pot index 2ed94168f2a..ab1483026ae 100644 --- a/addons/calendar/i18n/calendar.pot +++ b/addons/calendar/i18n/calendar.pot @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server saas~11.2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-03-22 14:11+0000\n" -"PO-Revision-Date: 2018-03-22 14:11+0000\n" +"POT-Creation-Date: 2018-06-06 08:01+0000\n" +"PO-Revision-Date: 2018-06-06 08:01+0000\n" "Last-Translator: <>\n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -865,6 +865,13 @@ msgstr "" msgid "Meeting" msgstr "" +#. module: calendar +#: code:addons/calendar/models/calendar.py:933 +#: code:addons/calendar/models/calendar.py:937 +#, python-format +msgid "Meeting '%s' starts '%s' and ends '%s'" +msgstr "" + #. module: calendar #: model:ir.ui.view,arch_db:calendar.view_calendar_event_form msgid "Meeting Details" diff --git a/addons/calendar/i18n/zh_CN.po b/addons/calendar/i18n/zh_CN.po index 8e768bb2786..37ccc737772 100644 --- a/addons/calendar/i18n/zh_CN.po +++ b/addons/calendar/i18n/zh_CN.po @@ -13,13 +13,14 @@ # 老窦 北京 <2662059195@qq.com>, 2018 # e2f , 2018 # e2f_cn c5 , 2018 +# John Lin , 2018 msgid "" msgstr "" "Project-Id-Version: Odoo Server saas~11.2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-03-22 14:11+0000\n" "PO-Revision-Date: 2018-03-22 14:11+0000\n" -"Last-Translator: e2f_cn c5 , 2018\n" +"Last-Translator: John Lin , 2018\n" "Language-Team: Chinese (China) (https://www.transifex.com/odoo/teams/41243/zh_CN/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -1062,7 +1063,7 @@ msgstr "邀请向导" #. module: calendar #: model:ir.model.fields,field_description:calendar.field_calendar_event__is_highlighted msgid "Is the Event Highlighted" -msgstr "" +msgstr "是事件高亮" #. module: calendar #: selection:calendar.event,byday:0 diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py index 161130e1e94..7a338adc52b 100644 --- a/addons/calendar/models/calendar.py +++ b/addons/calendar/models/calendar.py @@ -930,9 +930,13 @@ class Meeting(models.Model): def _check_closing_date(self): for meeting in self: if meeting.start_datetime and meeting.stop_datetime and meeting.stop_datetime < meeting.start_datetime: - raise ValidationError(_('Ending datetime cannot be set before starting datetime.')) + raise ValidationError(_('Ending datetime cannot be set before starting datetime.') + "\n" + + _("Meeting '%s' starts '%s' and ends '%s'") % (meeting.name, meeting.start_datetime, meeting.stop_datetime) + ) if meeting.start_date and meeting.stop_date and meeting.stop_date < meeting.start_date: - raise ValidationError(_('Ending date cannot be set before starting date.')) + raise ValidationError(_('Ending date cannot be set before starting date.') + "\n" + + _("Meeting '%s' starts '%s' and ends '%s'") % (meeting.name, meeting.start_date, meeting.stop_date) + ) @api.onchange('start_datetime', 'duration') def _onchange_duration(self): diff --git a/addons/crm/i18n/tr.po b/addons/crm/i18n/tr.po index 1dc1294b67b..503973f4250 100644 --- a/addons/crm/i18n/tr.po +++ b/addons/crm/i18n/tr.po @@ -1021,7 +1021,7 @@ msgstr "Atamaya Zorla" #. module: crm #: model:ir.ui.view,arch_db:crm.res_config_settings_view_form msgid "Format phone numbers based on national conventions" -msgstr "" +msgstr "Telefon numaralarını ulusal sözleşmelere göre biçimlendirme" #. module: crm #: code:addons/crm/models/crm_lead.py:588 @@ -1869,7 +1869,7 @@ msgstr "Telefon" #. module: crm #: model:ir.model.fields,field_description:crm.field_res_config_settings__module_crm_phone_validation msgid "Phone Formatting" -msgstr "" +msgstr "Telefon Biçimlendirmesi" #. module: crm #: code:addons/crm/models/crm_team.py:165 diff --git a/addons/crm_project/i18n/tr.po b/addons/crm_project/i18n/tr.po index f1d4348f477..9406b7286ab 100644 --- a/addons/crm_project/i18n/tr.po +++ b/addons/crm_project/i18n/tr.po @@ -91,4 +91,4 @@ msgstr "Proje" #. module: crm_project #: model:ir.model,name:crm_project.model_crm_lead_convert2task msgid "crm.lead.convert2task" -msgstr "" +msgstr "crm.lead.convert2task" diff --git a/addons/delivery/models/stock_picking.py b/addons/delivery/models/stock_picking.py index 86517b1d0ec..908b7587c51 100644 --- a/addons/delivery/models/stock_picking.py +++ b/addons/delivery/models/stock_picking.py @@ -162,7 +162,8 @@ class StockPicking(models.Model): if self.carrier_id.free_over and self.sale_id and self.sale_id._compute_amount_total_without_delivery() >= self.carrier_id.amount: res['exact_price'] = 0.0 self.carrier_price = res['exact_price'] - self.carrier_tracking_ref = res['tracking_number'] + if res['tracking_number']: + self.carrier_tracking_ref = res['tracking_number'] order_currency = self.sale_id.currency_id or self.company_id.currency_id msg = _("Shipment sent to carrier %s for shipping with tracking number %s
Cost: %.2f %s") % (self.carrier_id.name, self.carrier_tracking_ref, self.carrier_price, order_currency.name) self.message_post(body=msg) diff --git a/addons/event/i18n/es.po b/addons/event/i18n/es.po index f55e9ffd371..418660796de 100644 --- a/addons/event/i18n/es.po +++ b/addons/event/i18n/es.po @@ -206,6 +206,168 @@ msgid "" "\n" " " msgstr "" +"\n" +"
\n" +"% set date_begin = format_tz(object.event_id.date_begin, tz='UTC', format='%Y%m%dT%H%M%SZ')\n" +"% set date_end = format_tz(object.event_id.date_end, tz='UTC', format='%Y%m%dT%H%M%SZ')\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +" \"${object.company_id.name}\"\n" +" \n" +" \n" +" % if 'website_url' in object.event_id and object.event_id.website_url:\n" +" Ver evento\n" +" % endif\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +"

\n" +" Hi,\n" +"

\n" +" Nos alegra confirmarle su registro en el evento:\n" +"

\n" +"
    \n" +"
  • Evento: \n" +" % if 'website_url' in object.event_id and object.event_id.website_url:\n" +" ${object.event_id.name}\n" +" % else:\n" +" ${object.event_id.name}\n" +" % endif\n" +"
  • \n" +"
  • Asistente: ${object.name}
  • \n" +"
\n" +"

\n" +" Nos vemos pronto,\n" +"

\n" +"

\n" +" \n" +" --
\n" +" % if object.event_id.organizer_id:\n" +" ${object.event_id.organizer_id.name}\n" +" % else:\n" +" Los organizadores.\n" +" % endif\n" +"
\n" +"

\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" % if object.event_id.address_id.country_id.name:\n" +" \n" +" \n" +" % endif\n" +" \n" +"
\n" +" \n" +" \n" +"
Desde ${object.event_id.date_begin_located}
\n" +"
Hasta ${object.event_id.date_end_located}
\n" +"
TZ ${object.event_id.date_tz}
\n" +"
\n" +" \n" +" \n" +" % set location = ''\n" +" % if object.event_id.address_id.name:\n" +"

${object.event_id.address_id.name}

\n" +" % endif\n" +" % if object.event_id.address_id.street:\n" +"

${object.event_id.address_id.street}

\n" +" % set location = object.event_id.address_id.street\n" +" % endif\n" +" % if object.event_id.address_id.street2:\n" +"

${object.event_id.address_id.street2}

\n" +" % set location = '%s, %s' % (location, object.event_id.address_id.street2)\n" +" % endif\n" +"

\n" +" % if object.event_id.address_id.city:\n" +" ${object.event_id.address_id.city},\n" +" % set location = '%s, %s' % (location, object.event_id.address_id.city)\n" +" % endif\n" +" % if object.event_id.address_id.state_id.name:\n" +" ${object.event_id.address_id.state_id.name},\n" +" % set location = '%s, %s' % (location, object.event_id.address_id.state_id.name)\n" +" % endif\n" +" % if object.event_id.address_id.zip:\n" +" ${object.event_id.address_id.zip}\n" +" % set location = '%s, %s' % (location, object.event_id.address_id.zip)\n" +" % endif\n" +"

\n" +" % if object.event_id.address_id.country_id.name:\n" +"

${object.event_id.address_id.country_id.name}

\n" +" % set location = '%s, %s' % (location, object.event_id.address_id.country_id.name)\n" +" % endif\n" +"
\n" +" % if object.event_id.organizer_id:\n" +" \n" +" \n" +" \n" +" \n" +"
\n" +"

¿Preguntas sobre este evento?

\n" +"

Por favor contacte con el organizador:

\n" +"
    \n" +"
  • ${object.event_id.organizer_id.name}
  • \n" +" % if object.event_id.organizer_id.email\n" +"
  • Correo: ${object.event_id.organizer_id.email}
  • \n" +" % endif\n" +" % if object.event_id.organizer_id.phone\n" +"
  • Teléfono: ${object.event_id.organizer_id.phone}
  • \n" +" % endif\n" +"
\n" +"\n" +"
\n" +" % endif\n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" Añada este evento a su calendario\n" +" Google\n" +" Outlook\n" +" Yahoo\n" +"
\n" +"
\n" +" % if object.event_id.address_id:\n" +"
\n" +" \n" +" \n" +" \n" +"
\n" +" % endif\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +"
Enviado por ${object.company_id.name}
\n" +" % if 'website_url' in object.event_id and object.event_id.website_url:\n" +"
\n" +" Descubra todos nuestros eventos.\n" +"
\n" +" % endif\n" +"
\n" +"
\n" +" " #. module: event #: model:mail.template,body_html:event.event_reminder diff --git a/addons/event/views/res_partner_views.xml b/addons/event/views/res_partner_views.xml index 8fd4b774c5e..59d096efcd1 100644 --- a/addons/event/views/res_partner_views.xml +++ b/addons/event/views/res_partner_views.xml @@ -5,6 +5,7 @@ view.res.partner.form.event.inherited res.partner +
diff --git a/odoo/addons/base/views/res_partner_views.xml b/odoo/addons/base/views/res_partner_views.xml index 28ad85b892a..3370eaeb5ce 100644 --- a/odoo/addons/base/views/res_partner_views.xml +++ b/odoo/addons/base/views/res_partner_views.xml @@ -171,7 +171,7 @@
- + - + diff --git a/odoo/modules/loading.py b/odoo/modules/loading.py index 4dda572fb13..b9f4e2fdc7b 100644 --- a/odoo/modules/loading.py +++ b/odoo/modules/loading.py @@ -25,7 +25,8 @@ _logger = logging.getLogger(__name__) _test_logger = logging.getLogger('odoo.tests') -def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=None, report=None): +def load_module_graph(cr, graph, status=None, perform_checks=True, + skip_modules=None, report=None, models_to_check=None): """Migrates+Updates or Installs all module nodes from ``graph`` :param graph: graph of module nodes to load :param status: deprecated parameter, unused, left to avoid changing signature in 8.0 @@ -93,6 +94,9 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules= if kind in ('demo', 'test'): threading.currentThread().testing = False + if models_to_check is None: + models_to_check = set() + processed_modules = [] loaded_modules = [] registry = odoo.registry(cr.dbname) @@ -106,6 +110,8 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules= t0 = time.time() t0_sql = odoo.sql_db.sql_counter + models_updated = set() + for index, package in enumerate(graph, 1): module_name = package.name module_id = package.id @@ -127,10 +133,20 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules= model_names = registry.load(cr, package) loaded_modules.append(package.name) - if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'): + if (hasattr(package, 'init') or hasattr(package, 'update') + or package.state in ('to install', 'to upgrade')): + models_updated |= set(model_names) + models_to_check -= set(model_names) registry.setup_models(cr) registry.init_models(cr, model_names, {'module': package.name}) cr.commit() + elif package.state != 'to remove': + # The current module has simply been loaded. The models extended by this module + # and for which we updated the schema, must have their schema checked again. + # This is because the extension may have changed the model, + # e.g. adding required=True to an existing field, but the schema has not been + # updated by this module because it's not marked as 'to upgrade/to install'. + models_to_check |= set(model_names) & models_updated idref = {} @@ -225,9 +241,14 @@ def _check_module_names(cr, module_names): incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()]) _logger.warning('invalid module names, ignored: %s', ", ".join(incorrect_names)) -def load_marked_modules(cr, graph, states, force, progressdict, report, loaded_modules, perform_checks): +def load_marked_modules(cr, graph, states, force, progressdict, report, + loaded_modules, perform_checks, models_to_check=None): """Loads modules marked with ``states``, adding them to ``graph`` and ``loaded_modules`` and returns a list of installed/upgraded modules.""" + + if models_to_check is None: + models_to_check = set() + processed_modules = [] while True: cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(states),)) @@ -236,7 +257,10 @@ def load_marked_modules(cr, graph, states, force, progressdict, report, loaded_m break graph.add_modules(cr, module_list, force) _logger.debug('Updating graph with %d more modules', len(module_list)) - loaded, processed = load_module_graph(cr, graph, progressdict, report=report, skip_modules=loaded_modules, perform_checks=perform_checks) + loaded, processed = load_module_graph( + cr, graph, progressdict, report=report, skip_modules=loaded_modules, + perform_checks=perform_checks, models_to_check=models_to_check + ) processed_modules.extend(processed) loaded_modules.extend(loaded) if not processed: @@ -250,6 +274,8 @@ def load_modules(db, force_demo=False, status=None, update_module=False): if force_demo: force.append('demo') + models_to_check = set() + cr = db.cursor() try: if not odoo.modules.db.is_initialized(cr): @@ -278,7 +304,9 @@ def load_modules(db, force_demo=False, status=None, update_module=False): # processed_modules: for cleanup step after install # loaded_modules: to avoid double loading report = registry._assertion_report - loaded_modules, processed_modules = load_module_graph(cr, graph, status, perform_checks=update_module, report=report) + loaded_modules, processed_modules = load_module_graph( + cr, graph, status, perform_checks=update_module, + report=report, models_to_check=models_to_check) load_lang = tools.config.pop('load_language') if load_lang or update_module: @@ -333,11 +361,11 @@ def load_modules(db, force_demo=False, status=None, update_module=False): previously_processed = len(processed_modules) processed_modules += load_marked_modules(cr, graph, ['installed', 'to upgrade', 'to remove'], - force, status, report, loaded_modules, update_module) + force, status, report, loaded_modules, update_module, models_to_check) if update_module: processed_modules += load_marked_modules(cr, graph, ['to install'], force, status, report, - loaded_modules, update_module) + loaded_modules, update_module, models_to_check) registry.loaded = True registry.setup_models(cr) @@ -407,6 +435,16 @@ def load_modules(db, force_demo=False, status=None, update_module=False): cr.commit() return registry + # STEP 5.5: Verify extended fields on every model + # This will fix the schema of all models in a situation such as: + # - module A is loaded and defines model M; + # - module B is installed/upgraded and extends model M; + # - module C is loaded and extends model M; + # - module B and C depend on A but not on each other; + # The changes introduced by module C are not taken into account by the upgrade of B. + if models_to_check: + registry.init_models(cr, list(models_to_check), {'models_to_check': True}) + # STEP 6: verify custom views on every model if update_module: env = api.Environment(cr, SUPERUSER_ID, {}) diff --git a/odoo/modules/registry.py b/odoo/modules/registry.py index 712b25aefb3..9e180138a21 100644 --- a/odoo/modules/registry.py +++ b/odoo/modules/registry.py @@ -287,6 +287,8 @@ class Registry(Mapping): """ if 'module' in context: _logger.info('module %s: creating or updating database tables', context['module']) + elif context.get('models_to_check', False): + _logger.info("verifying fields for every extended model") env = odoo.api.Environment(cr, SUPERUSER_ID, context) models = [env[model_name] for model_name in model_names]