diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 954a8dc0c6f..2fe9f9536a8 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -189,7 +189,7 @@ class AccountInvoice(models.Model): @api.depends('move_id.line_ids.amount_residual') def _compute_payments(self): payment_lines = [] - for line in self.move_id.line_ids: + for line in self.move_id.line_ids.filtered(lambda l: l.account_id.id == self.account_id.id): payment_lines.extend(filter(None, [rp.credit_move_id.id for rp in line.matched_credit_ids])) payment_lines.extend(filter(None, [rp.debit_move_id.id for rp in line.matched_debit_ids])) self.payment_move_line_ids = self.env['account.move.line'].browse(list(set(payment_lines))) diff --git a/addons/account/report/account_aged_partner_balance.py b/addons/account/report/account_aged_partner_balance.py index 31a6110be9a..968f939d493 100644 --- a/addons/account/report/account_aged_partner_balance.py +++ b/addons/account/report/account_aged_partner_balance.py @@ -13,6 +13,10 @@ class ReportAgedPartnerBalance(models.AbstractModel): _name = 'report.account.report_agedpartnerbalance' def _get_partner_move_lines(self, account_type, date_from, target_move, period_length): + # This method can receive the context key 'include_nullified_amount' {Boolean} + # Do an invoice and a payment and unreconcile. The amount will be nullified + # By default, the partner wouldn't appear in this report. + # The context key allow it to appear periods = {} start = datetime.strptime(date_from, "%Y-%m-%d") for i in range(5)[::-1]: @@ -190,7 +194,7 @@ class ReportAgedPartnerBalance(models.AbstractModel): values['name'] = _('Unknown Partner') values['trust'] = False - if at_least_one_amount: + if at_least_one_amount or self._context.get('include_nullified_amount'): res.append(values) return res, total, lines diff --git a/addons/account/report/account_overdue_report.py b/addons/account/report/account_overdue_report.py index fd032197233..8afa71b4414 100644 --- a/addons/account/report/account_overdue_report.py +++ b/addons/account/report/account_overdue_report.py @@ -25,7 +25,7 @@ class ReportOverdue(models.AbstractModel): "FROM account_move_line l " "JOIN account_account_type at ON (l.user_type_id = at.id) " "JOIN account_move m ON (l.move_id = m.id) " - "WHERE l.partner_id IN %s AND at.type IN ('receivable', 'payable') AND NOT l.reconciled GROUP BY l.date, l.name, l.ref, l.date_maturity, l.partner_id, at.type, l.blocked, l.amount_currency, l.currency_id, l.move_id, m.name", (((fields.date.today(), ) + (tuple(partner_ids),)))) + "WHERE l.partner_id IN %s AND at.type IN ('receivable', 'payable') AND l.full_reconcile_id IS NULL GROUP BY l.date, l.name, l.ref, l.date_maturity, l.partner_id, at.type, l.blocked, l.amount_currency, l.currency_id, l.move_id, m.name", (((fields.date.today(), ) + (tuple(partner_ids),)))) for row in self.env.cr.dictfetchall(): res[row.pop('partner_id')].append(row) return res diff --git a/addons/account/report/account_partner_ledger.py b/addons/account/report/account_partner_ledger.py index fa756c4b977..399557504d7 100644 --- a/addons/account/report/account_partner_ledger.py +++ b/addons/account/report/account_partner_ledger.py @@ -14,7 +14,7 @@ class ReportPartnerLedger(models.AbstractModel): full_account = [] currency = self.env['res.currency'] query_get_data = self.env['account.move.line'].with_context(data['form'].get('used_context', {}))._query_get() - reconcile_clause = "" if data['form']['reconciled'] else ' AND "account_move_line".reconciled = false ' + reconcile_clause = "" if data['form']['reconciled'] else ' AND "account_move_line".full_reconcile_id IS NULL ' params = [partner.id, tuple(data['computed']['move_state']), tuple(data['computed']['account_ids'])] + query_get_data[2] query = """ SELECT "account_move_line".id, "account_move_line".date, j.code, acc.code as a_code, acc.name as a_name, "account_move_line".ref, m.name as move_name, "account_move_line".name, "account_move_line".debit, "account_move_line".credit, "account_move_line".amount_currency,"account_move_line".currency_id, c.symbol AS currency_code @@ -51,7 +51,7 @@ class ReportPartnerLedger(models.AbstractModel): return result = 0.0 query_get_data = self.env['account.move.line'].with_context(data['form'].get('used_context', {}))._query_get() - reconcile_clause = "" if data['form']['reconciled'] else ' AND "account_move_line".reconciled = false ' + reconcile_clause = "" if data['form']['reconciled'] else ' AND "account_move_line".full_reconcile_id IS NULL ' params = [partner.id, tuple(data['computed']['move_state']), tuple(data['computed']['account_ids'])] + query_get_data[2] query = """SELECT sum(""" + field + """) @@ -95,7 +95,7 @@ class ReportPartnerLedger(models.AbstractModel): AND NOT a.deprecated""", (tuple(data['computed']['ACCOUNT_TYPE']),)) data['computed']['account_ids'] = [a for (a,) in self.env.cr.fetchall()] params = [tuple(data['computed']['move_state']), tuple(data['computed']['account_ids'])] + query_get_data[2] - reconcile_clause = "" if data['form']['reconciled'] else ' AND "account_move_line".reconciled = false ' + reconcile_clause = "" if data['form']['reconciled'] else ' AND "account_move_line".full_reconcile_id IS NULL ' query = """ SELECT DISTINCT "account_move_line".partner_id FROM """ + query_get_data[0] + """, account_account AS account, account_move AS am diff --git a/addons/board/static/src/js/dashboard.js b/addons/board/static/src/js/dashboard.js index 88f179d0644..e9dec85739f 100644 --- a/addons/board/static/src/js/dashboard.js +++ b/addons/board/static/src/js/dashboard.js @@ -276,6 +276,14 @@ var DashBoard = form_common.FormWidget.extend({ }; }); } + var calendar = am.inner_widget.views.calendar; + if (calendar) { + calendar.loaded.done(function () { + calendar.controller.open_event = function (id, title) { + new_form_action(Number(id)); + }; + }); + } } }); am.do_action = self.do_action.bind(self); diff --git a/addons/l10n_fr_fec/wizard/account_fr_fec.py b/addons/l10n_fr_fec/wizard/account_fr_fec.py index 8f57d844e0a..3b8ed12851f 100644 --- a/addons/l10n_fr_fec/wizard/account_fr_fec.py +++ b/addons/l10n_fr_fec/wizard/account_fr_fec.py @@ -221,7 +221,7 @@ class AccountFrFec(models.TransientModel): replace(MIN(aa.name), '|', '/') AS CompteLib, CASE WHEN rp.ref IS null OR rp.ref = '' THEN COALESCE('ID ' || rp.id, '') - ELSE rp.ref + ELSE replace(rp.ref, '|', '/') END AS CompAuxNum, COALESCE(replace(rp.name, '|', '/'), '') AS CompAuxLib, @@ -279,7 +279,7 @@ class AccountFrFec(models.TransientModel): replace(aa.name, '|', '/') AS CompteLib, CASE WHEN rp.ref IS null OR rp.ref = '' THEN COALESCE('ID ' || rp.id, '') - ELSE rp.ref + ELSE replace(rp.ref, '|', '/') END AS CompAuxNum, COALESCE(replace(rp.name, '|', '/'), '') AS CompAuxLib, diff --git a/addons/membership/models/partner.py b/addons/membership/models/partner.py index 8ee9bee2f5a..2c8227844be 100644 --- a/addons/membership/models/partner.py +++ b/addons/membership/models/partner.py @@ -51,7 +51,7 @@ class Partner(models.Model): 'member_lines.account_invoice_line.invoice_id.invoice_line_ids', 'member_lines.account_invoice_line.invoice_id.payment_ids', 'free_member', - 'member_lines.date_to', 'member_lines.date_from', + 'member_lines.date_to', 'member_lines.date_from', 'member_lines.date_cancel', 'membership_state', 'associate_member.membership_state') def _compute_membership_start(self): @@ -65,7 +65,7 @@ class Partner(models.Model): 'member_lines.account_invoice_line.invoice_id.invoice_line_ids', 'member_lines.account_invoice_line.invoice_id.payment_ids', 'free_member', - 'member_lines.date_to', 'member_lines.date_from', + 'member_lines.date_to', 'member_lines.date_from', 'member_lines.date_cancel', 'membership_state', 'associate_member.membership_state') def _compute_membership_stop(self): @@ -79,7 +79,7 @@ class Partner(models.Model): 'member_lines.account_invoice_line.invoice_id.invoice_line_ids', 'member_lines.account_invoice_line.invoice_id.payment_ids', 'free_member', - 'member_lines.date_to', 'member_lines.date_from', + 'member_lines.date_to', 'member_lines.date_from', 'member_lines.date_cancel', 'membership_state', 'associate_member.membership_state') def _compute_membership_cancel(self): diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 0f8dbdb801e..6e12dc8b114 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -1979,7 +1979,9 @@ exports.Order = Backbone.Model.extend({ } }) - unit_price = line.compute_all(mapped_included_taxes, unit_price, 1, this.pos.currency.rounding, true).total_excluded; + if (mapped_included_taxes.length > 0) { + unit_price = line.compute_all(mapped_included_taxes, unit_price, 1, this.pos.currency.rounding, true).total_excluded; + } line.set_unit_price(unit_price); } diff --git a/addons/project_issue/models/project_issue.py b/addons/project_issue/models/project_issue.py index d4f5e94209f..c6d883a6627 100644 --- a/addons/project_issue/models/project_issue.py +++ b/addons/project_issue/models/project_issue.py @@ -209,11 +209,11 @@ class ProjectIssue(models.Model): @api.multi def _track_template(self, tracking): - self.ensure_one() res = super(ProjectIssue, self)._track_template(tracking) - changes, dummy = tracking[self.id] - if 'stage_id' in changes and self.stage_id.mail_template_id: - res['stage_id'] = (self.stage_id.mail_template_id, {'composition_mode': 'mass_mail'}) + test_issue = self[0] + changes, tracking_value_ids = tracking[test_issue.id] + if 'stage_id' in changes and test_issue.stage_id.mail_template_id: + res['stage_id'] = (test_issue.stage_id.mail_template_id, {'composition_mode': 'mass_mail'}) return res def _track_subtype(self, init_values): diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index f4b84ab8b06..d142f69a870 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -492,7 +492,7 @@ class SaleOrder(models.Model): report_pages.append([]) # Append category to current report page report_pages[-1].append({ - 'name': category and category.name or 'Uncategorized', + 'name': category and category.name or _('Uncategorized'), 'subtotal': category and category.subtotal, 'pagebreak': category and category.pagebreak, 'lines': list(lines) diff --git a/addons/sales_team/security/sales_team_security.xml b/addons/sales_team/security/sales_team_security.xml index 904f7a3c04d..9e2e3f9939a 100644 --- a/addons/sales_team/security/sales_team_security.xml +++ b/addons/sales_team/security/sales_team_security.xml @@ -22,14 +22,6 @@ - - - Own Salesteam - - [('member_ids', 'in', user.id)] - - - All Salesteam diff --git a/addons/stock/models/stock_warehouse.py b/addons/stock/models/stock_warehouse.py index c5b3ba0d490..ad1f0ea07e5 100644 --- a/addons/stock/models/stock_warehouse.py +++ b/addons/stock/models/stock_warehouse.py @@ -653,11 +653,11 @@ class Warehouse(models.Model): def _get_sequence_values(self): return { - 'in_type_id': {'name': self.name + _('Sequence in'), 'prefix': self.code + '/IN/', 'padding': 5}, - 'out_type_id': {'name': self.name + _('Sequence out'), 'prefix': self.code + '/OUT/', 'padding': 5}, - 'pack_type_id': {'name': self.name + _('Sequence packing'), 'prefix': self.code + '/PACK/', 'padding': 5}, - 'pick_type_id': {'name': self.name + _('Sequence picking'), 'prefix': self.code + '/PICK/', 'padding': 5}, - 'int_type_id': {'name': self.name + _('Sequence internal'), 'prefix': self.code + '/INT/', 'padding': 5}, + 'in_type_id': {'name': self.name + ' ' + _('Sequence in'), 'prefix': self.code + '/IN/', 'padding': 5}, + 'out_type_id': {'name': self.name + ' ' + _('Sequence out'), 'prefix': self.code + '/OUT/', 'padding': 5}, + 'pack_type_id': {'name': self.name + ' ' + _('Sequence packing'), 'prefix': self.code + '/PACK/', 'padding': 5}, + 'pick_type_id': {'name': self.name + ' ' + _('Sequence picking'), 'prefix': self.code + '/PICK/', 'padding': 5}, + 'int_type_id': {'name': self.name + ' ' + _('Sequence internal'), 'prefix': self.code + '/INT/', 'padding': 5}, } @api.multi diff --git a/addons/stock_landed_costs/models/stock_landed_cost.py b/addons/stock_landed_costs/models/stock_landed_cost.py index c4a6beaf307..43c125415cf 100644 --- a/addons/stock_landed_costs/models/stock_landed_cost.py +++ b/addons/stock_landed_costs/models/stock_landed_cost.py @@ -15,7 +15,7 @@ class LandedCost(models.Model): _inherit = 'mail.thread' name = fields.Char( - 'Name', default=lambda self: self.env['ir.sequence'].next_by_code('stock.landed.cost'), + 'Name', default=lambda self: _('New'), copy=False, readonly=True, track_visibility='always') date = fields.Date( 'Date', default=fields.Date.context_today, @@ -51,6 +51,12 @@ class LandedCost(models.Model): def _compute_total_amount(self): self.amount_total = sum(line.price_unit for line in self.cost_lines) + @api.model + def create(self, vals): + if vals.get('name', _('New')) == _('New'): + vals['name'] = self.env['ir.sequence'].next_by_code('stock.landed.cost') + return super(LandedCost, self).create(vals) + @api.multi def unlink(self): self.button_cancel() diff --git a/doc/cla/corporate/kazacube.md b/doc/cla/corporate/kazacube.md new file mode 100644 index 00000000000..ae4d2868080 --- /dev/null +++ b/doc/cla/corporate/kazacube.md @@ -0,0 +1,16 @@ +Morocco, 2017-10-11 + +KAZACUBE SARL agrees to the terms of the Odoo Corporate Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Damien DELANGLE ddelangle@kazacube.com https://github.com/kazacube-damien + +List of contributors: + +ddelangle@kazacube.com https://github.com/kazacube-damien +cloudmaster@kazacube.com https://github.com/sharecodekzc diff --git a/doc/cla/individual/SimoRubi.md b/doc/cla/individual/SimoRubi.md new file mode 100644 index 00000000000..07a69054170 --- /dev/null +++ b/doc/cla/individual/SimoRubi.md @@ -0,0 +1,11 @@ +Italy, 2018-02-05 + +I hereby agree to the terms of the Odoo Individual Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Simone Rubino simone.rubino@agilebg.com https://github.com/SimoRubi diff --git a/doc/cla/individual/kurkop.md b/doc/cla/individual/kurkop.md index ead18fbc4cb..99527b55009 100644 --- a/doc/cla/individual/kurkop.md +++ b/doc/cla/individual/kurkop.md @@ -9,3 +9,4 @@ declaration. Signed, Juan Pablo Arias jpa@devco.co https://github.com/kurkop +Juan Pablo Arias j@kurlabs.com https://github.com/kurkop diff --git a/doc/reference/mobile.rst b/doc/reference/mobile.rst index db9dc1343f4..4777631f4b0 100644 --- a/doc/reference/mobile.rst +++ b/doc/reference/mobile.rst @@ -22,7 +22,7 @@ Vibration, Notification, Toast through Odoo Web (via JavaScript). For this, you do not need to be a mobile developer, if you know Odoo JavaScript API you can access all available mobile features. -.. warning:: This features works with **Odoo Enterprise 10.0+** only +.. warning:: These features work with **Odoo Enterprise 10.0+** only How does it work? ================= diff --git a/odoo/addons/base/res/res_country_data.xml b/odoo/addons/base/res/res_country_data.xml index c56247a98cb..8ff0caa1ef0 100644 --- a/odoo/addons/base/res/res_country_data.xml +++ b/odoo/addons/base/res/res_country_data.xml @@ -1226,6 +1226,7 @@ + Peru diff --git a/odoo/addons/base/res/res_currency_data.xml b/odoo/addons/base/res/res_currency_data.xml index 8746f4d0b1b..e3088952ca0 100644 --- a/odoo/addons/base/res/res_currency_data.xml +++ b/odoo/addons/base/res/res_currency_data.xml @@ -781,7 +781,7 @@ MAD - د.م. + DH 0.01 diff --git a/odoo/addons/test_new_api/tests/test_new_fields.py b/odoo/addons/test_new_api/tests/test_new_fields.py index e7579f5c909..b2ff0c1eb58 100644 --- a/odoo/addons/test_new_api/tests/test_new_fields.py +++ b/odoo/addons/test_new_api/tests/test_new_fields.py @@ -228,6 +228,13 @@ class TestFields(common.TransactionCase): self.assertEqual(ewan.parent, cath) self.assertEqual(ewan.name, "Erwan") + # write on non-stored inverse field on severals records + foo1 = Category.create({'name': 'Foo'}) + foo2 = Category.create({'name': 'Foo'}) + (foo1 + foo2).write({'display_name': 'Bar'}) + self.assertEqual(foo1.name, 'Bar') + self.assertEqual(foo2.name, 'Bar') + record = self.env['test_new_api.compute.inverse'] # create/write on 'foo' should only invoke the compute method diff --git a/odoo/fields.py b/odoo/fields.py index 4d07fa26642..7ed3d5762c0 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -69,6 +69,10 @@ def copy_cache(records, env): for record_id in record_ids: if record_id in src_cache: # copy the cached value as such + if isinstance(src_cache[record_id], FailedValue): + # But not if it's a FailedValue, which often is an access error + # because the other environment (eg. sudo()) is well expected to have access. + continue value = dst_cache[record_id] = src_cache[record_id] if field.relational and isinstance(value, tuple): todo[field.comodel_name].update(value) @@ -1535,6 +1539,9 @@ class Date(Field): """ Convert a :class:`date` value into the format expected by the ORM. """ return value.strftime(DATE_FORMAT) if value else False + def convert_to_column(self, value, record, values=None): + return super(Date, self).convert_to_column(value or None, record, values) + def convert_to_cache(self, value, record, validate=True): if not value: return False @@ -1604,6 +1611,9 @@ class Datetime(Field): """ Convert a :class:`datetime` value into the format expected by the ORM. """ return value.strftime(DATETIME_FORMAT) if value else False + def convert_to_column(self, value, record, values=None): + return super(Datetime, self).convert_to_column(value or None, record, values) + def convert_to_cache(self, value, record, validate=True): if not value: return False diff --git a/odoo/models.py b/odoo/models.py index ae54c55aae7..b9fd0b014c6 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -3525,15 +3525,28 @@ class BaseModel(object): self._write(old_vals) if new_vals: - # put the values of pure new-style fields into cache, and inverse them self.modified(set(new_vals) - set(old_vals)) - for record in self: - record._cache.update(record._convert_to_cache(new_vals, update=True)) + + # put the values of fields into cache, and inverse them for key in new_vals: - self._fields[key].determine_inverse(self) + field = self._fields[key] + # If a field is not stored, its inverse method will probably + # write on its dependencies, which will invalidate the field + # on all records. We therefore inverse the field one record + # at a time. + batches = [self] if field.store else list(self) + for records in batches: + for record in records: + record._cache.update( + record._convert_to_cache(new_vals, update=True) + ) + field.determine_inverse(records) + self.modified(set(new_vals) - set(old_vals)) + # check Python constraints for inversed fields self._validate_fields(set(new_vals) - set(old_vals)) + # recompute new-style fields if self.env.recompute and self._context.get('recompute', True): self.recompute() diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py index 8d6703a5d17..016559a69ed 100644 --- a/odoo/tools/translate.py +++ b/odoo/tools/translate.py @@ -463,7 +463,7 @@ def quote(s): .replace('\n', '\\n"\n"') re_escaped_char = re.compile(r"(\\.)") -re_escaped_replacements = {'n': '\n', } +re_escaped_replacements = {'n': '\n', 't': '\t',} def _sub_replacement(match_obj): return re_escaped_replacements.get(match_obj.group(1)[1], match_obj.group(1)[1])