diff --git a/addons/account/models/account_journal_dashboard.py b/addons/account/models/account_journal_dashboard.py index a769a3f8d17..d9cf58720ea 100644 --- a/addons/account/models/account_journal_dashboard.py +++ b/addons/account/models/account_journal_dashboard.py @@ -219,7 +219,7 @@ class account_journal(models.Model): data as its first element, and the arguments dictionary to use to run it as its second. """ - return ("""SELECT state, residual_signed as amount_total, currency_id AS currency + return ("""SELECT state, residual_signed as amount_total, currency_id AS currency, type FROM account_invoice WHERE journal_id = %(journal_id)s AND state = 'open';""", {'journal_id':self.id}) @@ -229,7 +229,7 @@ class account_journal(models.Model): gather the bills in draft state data, and the arguments dictionary to use to run it as its second. """ - return ("""SELECT state, amount_total, currency_id AS currency + return ("""SELECT state, amount_total, currency_id AS currency, type FROM account_invoice WHERE journal_id = %(journal_id)s AND state = 'draft';""", {'journal_id':self.id}) @@ -242,7 +242,9 @@ class account_journal(models.Model): for result in results_dict: cur = self.env['res.currency'].browse(result.get('currency')) rslt_count += 1 - rslt_sum += cur.compute(result.get('amount_total'), target_currency) + + type_factor = result.get('type') in ('in_refund', 'out_refund') and -1 or 1 + rslt_sum += type_factor * cur.compute(result.get('amount_total'), target_currency) return (rslt_count, rslt_sum) @api.multi diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index 97922186eb9..0f400253a18 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -299,6 +299,17 @@ class MrpProduction(models.Model): self.location_src_id = self.picking_type_id.default_location_src_id.id or location.id self.location_dest_id = self.picking_type_id.default_location_dest_id.id or location.id + @api.multi + def write (self, vals): + res = super(MrpProduction, self).write(vals) + if 'date_planned_start' in vals: + moves = (self.mapped('move_raw_ids') + self.mapped('move_finished_ids')).filtered( + lambda r: r.state not in ['done', 'cancel']) + moves.write({ + 'date_expected': vals['date_planned_start'], + }) + return res + @api.model def create(self, values): if not values.get('name', False) or values['name'] == _('New'): diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py index 64c2bcd22cb..1099d8b549e 100644 --- a/addons/mrp/models/stock_move.py +++ b/addons/mrp/models/stock_move.py @@ -137,12 +137,12 @@ class StockMove(models.Model): If you want to cancel this MO, please change the consumed quantities to 0.')) return super(StockMove, self)._action_cancel() - def _action_confirm(self, merge=True): + def _action_confirm(self, merge=True, merge_into=False): moves = self.env['stock.move'] for move in self: moves |= move.action_explode() # we go further with the list of ids potentially changed by action_explode - return super(StockMove, moves)._action_confirm(merge=merge) + return super(StockMove, moves)._action_confirm(merge=merge, merge_into=merge_into) def action_explode(self): """ Explodes pickings """ diff --git a/addons/payment_authorize/models/authorize_request.py b/addons/payment_authorize/models/authorize_request.py index af710b280ba..138c270e35d 100644 --- a/addons/payment_authorize/models/authorize_request.py +++ b/addons/payment_authorize/models/authorize_request.py @@ -155,13 +155,13 @@ class AuthorizeAPI(): billTo = etree.SubElement(payment_profile, "billTo") etree.SubElement(billTo, "address").text = (partner.street or '' + (partner.street2 if partner.street2 else '')) or None - missing_fields = [partner._fields[field].string for field in ['city', 'country_id', 'zip'] if not partner[field]] + missing_fields = [partner._fields[field].string for field in ['city', 'country_id'] if not partner[field]] if missing_fields: raise ValidationError({'missing_fields': missing_fields}) etree.SubElement(billTo, "city").text = partner.city etree.SubElement(billTo, "state").text = partner.state_id.name or None - etree.SubElement(billTo, "zip").text = partner.zip + etree.SubElement(billTo, "zip").text = partner.zip or '' etree.SubElement(billTo, "country").text = partner.country_id.name or None payment = etree.SubElement(payment_profile, "payment") creditCard = etree.SubElement(payment, "creditCard") diff --git a/addons/sale_timesheet/models/account_invoice.py b/addons/sale_timesheet/models/account_invoice.py index 3c1b3b78ae0..ddc3fb0a3c9 100644 --- a/addons/sale_timesheet/models/account_invoice.py +++ b/addons/sale_timesheet/models/account_invoice.py @@ -2,7 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import api, fields, models, _ -from odoo.tools.float_utils import float_round +from odoo.tools.float_utils import float_round, float_is_zero class AccountInvoice(models.Model): @@ -95,8 +95,11 @@ class AccountInvoice(models.Model): if index+1 != len(no_zero_timesheet_revenue): price_subtotal_inv = invoice_line.currency_id.compute(invoice_line.price_subtotal, timesheet_line.currency_id) price_subtotal_sol = timesheet_line.so_line.currency_id.compute(timesheet_line.so_line.price_subtotal, timesheet_line.currency_id) - line_revenue = timesheet_line.timesheet_revenue * price_subtotal_inv / price_subtotal_sol - total_revenue_per_currency[timesheet_line.currency_id.id] += line_revenue + if not float_is_zero(price_subtotal_sol, precision_rounding=timesheet_line.currency_id.rounding): + line_revenue = timesheet_line.timesheet_revenue * price_subtotal_inv / price_subtotal_sol + total_revenue_per_currency[timesheet_line.currency_id.id] += line_revenue + else: + total_revenue_per_currency[timesheet_line.currency_id.id] += timesheet_line.timesheet_revenue else: # last line: add the difference to avoid rounding problem last_price_subtotal_inv = invoice_line.currency_id.compute(invoice_line.price_subtotal, timesheet_line.currency_id) total_revenue = sum([self.env['res.currency'].browse(currency_id).compute(amount, timesheet_line.currency_id) for currency_id, amount in total_revenue_per_currency.items()]) diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index 379ae43a456..5ea5f48c4cb 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -542,7 +542,7 @@ class StockMove(models.Model): move.product_uom.id, move.restrict_partner_id.id, move.scrapped, move.origin_returned_move_id.id ] - def _merge_moves(self): + def _merge_moves(self, merge_into=False): """ This method will, for each move in `self`, go up in their linked picking and try to find in their existing moves a candidate into which we can merge the move. :return: Recordset of moves passed to this method. If some of the passed moves were merged @@ -550,12 +550,19 @@ class StockMove(models.Model): """ distinct_fields = self._prepare_merge_moves_distinct_fields() + candidate_moves_list = [] + if not merge_into: + for picking in self.mapped('picking_id'): + candidate_moves_list.append(picking.move_lines) + else: + candidate_moves_list.append(merge_into | self) + # Move removed after merge moves_to_unlink = self.env['stock.move'] moves_to_merge = [] - for picking in self.mapped('picking_id'): + for candidate_moves in candidate_moves_list: # First step find move to merge. - for k, g in groupby(sorted(picking.move_lines, key=self._prepare_merge_move_sort_method), key=itemgetter(*distinct_fields)): + for k, g in groupby(sorted(candidate_moves, key=self._prepare_merge_move_sort_method), key=itemgetter(*distinct_fields)): moves = self.env['stock.move'].concat(*g).filtered(lambda m: m.state not in ('done', 'cancel', 'draft')) # If we have multiple records we will merge then in a single one. if len(moves) > 1: @@ -709,7 +716,7 @@ class StockMove(models.Model): 'location_dest_id': self.location_dest_id.id, } - def _action_confirm(self, merge=True): + def _action_confirm(self, merge=True, merge_into=False): """ Confirms stock move or put it in waiting if it's linked to another move. :param: merge: According to this boolean, a newly confirmed move will be merged in another move of the same picking sharing its characteristics. @@ -749,7 +756,7 @@ class StockMove(models.Model): moves._assign_picking() self._push_apply() if merge: - return self._merge_moves() + return self._merge_moves(merge_into=merge_into) return self def _prepare_procurement_values(self): @@ -985,7 +992,7 @@ class StockMove(models.Model): The rationale for the creation of an extra move is the application of a potential push rule that will handle the extra quantities. """ - extra_move = self.env['stock.move'] + extra_move = self rounding = self.product_uom.rounding # moves created after the picking is assigned do not have `product_uom_qty`, but we shouldn't create extra moves for them if float_compare(self.quantity_done, self.product_uom_qty, precision_rounding=rounding) > 0: @@ -995,7 +1002,11 @@ class StockMove(models.Model): precision_rounding=self.product_uom.rounding, rounding_method ='UP') extra_move_vals = self._prepare_extra_move_vals(extra_move_quantity) - extra_move = self.copy(default=extra_move_vals)._action_confirm() + extra_move = self.copy(default=extra_move_vals) + if extra_move.picking_id: + extra_move = extra_move._action_confirm(merge_into=self) + else: + extra_move = extra_move._action_confirm() # link it to some move lines. We don't need to do it for move since they should be merged. if self.exists() and not self.picking_id: @@ -1034,7 +1045,9 @@ class StockMove(models.Model): for move in moves: if move.state == 'cancel' or move.quantity_done <= 0: continue - moves_todo |= move + # extra move will not be merged in mrp + if not move.picking_id: + moves_todo |= move moves_todo |= move._create_extra_move() # Split moves where necessary and move quants @@ -1064,7 +1077,7 @@ class StockMove(models.Model): .filtered(lambda p: p.quant_ids and len(p.quant_ids) > 1): if len(result_package.quant_ids.mapped('location_id')) > 1: raise UserError(_('You should not put the contents of a package in different locations.')) - picking = self and self[0].picking_id or False + picking = moves_todo and moves_todo[0].picking_id or False moves_todo.write({'state': 'done', 'date': fields.Datetime.now()}) moves_todo.mapped('move_dest_ids')._action_assign() diff --git a/addons/stock/models/stock_move_line.py b/addons/stock/models/stock_move_line.py index 9f4eacbd725..902446a6a49 100644 --- a/addons/stock/models/stock_move_line.py +++ b/addons/stock/models/stock_move_line.py @@ -403,6 +403,7 @@ class StockMoveLine(models.Model): ml_to_delete.unlink() # Now, we can actually move the quant. + done_ml = self.env['stock.move.line'] for ml in self - ml_to_delete: if ml.product_id.type == 'product': Quant = self.env['stock.quant'] @@ -411,7 +412,7 @@ class StockMoveLine(models.Model): # if this move line is force assigned, unreserve elsewhere if needed if not ml.location_id.should_bypass_reservation() and float_compare(ml.qty_done, ml.product_qty, precision_rounding=rounding) > 0: extra_qty = ml.qty_done - ml.product_qty - ml._free_reservation(ml.product_id, ml.location_id, extra_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id) + ml._free_reservation(ml.product_id, ml.location_id, extra_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, ml_to_ignore=done_ml) # unreserve what's been reserved if not ml.location_id.should_bypass_reservation() and ml.product_id.type == 'product' and ml.product_qty: try: @@ -430,8 +431,12 @@ class StockMoveLine(models.Model): Quant._update_available_quantity(ml.product_id, ml.location_id, -taken_from_untracked_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id) Quant._update_available_quantity(ml.product_id, ml.location_id, taken_from_untracked_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id) Quant._update_available_quantity(ml.product_id, ml.location_dest_id, quantity, lot_id=ml.lot_id, package_id=ml.result_package_id, owner_id=ml.owner_id, in_date=in_date) + done_ml |= ml # Reset the reserved quantity as we just moved it to the destination location. - (self - ml_to_delete).with_context(bypass_reservation_update=True).write({'product_uom_qty': 0.00}) + (self - ml_to_delete).with_context(bypass_reservation_update=True).write({ + 'product_uom_qty': 0.00, + 'date': fields.Datetime.now(), + }) def _log_message(self, record, move, template, vals): data = vals.copy() @@ -449,13 +454,19 @@ class StockMoveLine(models.Model): data['owner_name'] = self.env['res.partner'].browse(vals.get('owner_id')).name record.message_post_with_view(template, values={'move': move, 'vals': dict(vals, **data)}, subtype_id=self.env.ref('mail.mt_note').id) - def _free_reservation(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None): + def _free_reservation(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None, ml_to_ignore=None): """ When editing a done move line or validating one with some forced quantities, it is possible to impact quants that were not reserved. It is therefore necessary to edit or unlink the move lines that reserved a quantity now unavailable. + + :param ml_to_ignore: recordset of `stock.move.line` that should NOT be unreserved """ self.ensure_one() + if ml_to_ignore is None: + ml_to_ignore = self.env['stock.move.line'] + ml_to_ignore |= self + # Check the available quantity, with the `strict` kw set to `True`. If the available # quantity is greather than the quantity now unavailable, there is nothing to do. available_quantity = self.env['stock.quant']._get_available_quantity( @@ -472,7 +483,7 @@ class StockMoveLine(models.Model): ('owner_id', '=', owner_id.id if owner_id else False), ('package_id', '=', package_id.id if package_id else False), ('product_qty', '>', 0.0), - ('id', '!=', self.id), + ('id', 'not in', ml_to_ignore.ids), ] oudated_candidates = self.env['stock.move.line'].search(oudated_move_lines_domain) diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py index fa6f4fae341..6782bbb5ac0 100644 --- a/addons/stock/models/stock_picking.py +++ b/addons/stock/models/stock_picking.py @@ -300,11 +300,16 @@ class Picking(models.Model): product_id = fields.Many2one('product.product', 'Product', related='move_lines.product_id') show_operations = fields.Boolean(compute='_compute_show_operations') show_lots_text = fields.Boolean(compute='_compute_show_lots_text') + has_tracking = fields.Boolean(compute='_compute_has_tracking') _sql_constraints = [ ('name_uniq', 'unique(name, company_id)', 'Reference must be unique per company!'), ] + def _compute_has_tracking(self): + for picking in self: + picking.has_tracking = any(m.has_tracking != 'none' for m in picking.move_lines) + @api.depends('picking_type_id.show_operations') def _compute_show_operations(self): for picking in self: diff --git a/addons/stock/models/stock_traceability.py b/addons/stock/models/stock_traceability.py index 3b683002c05..633fb1b785e 100644 --- a/addons/stock/models/stock_traceability.py +++ b/addons/stock/models/stock_traceability.py @@ -90,6 +90,12 @@ class MrpStockReport(models.TransientModel): ref = move_line.move_id.scrap_ids[0].name return res_model, res_id, ref + @api.model + def _quantity_to_str(self, from_uom, to_uom, qty): + """ workaround to apply the float rounding logic of t-esc on data prepared server side """ + qty = from_uom._compute_quantity(qty, to_uom, rounding_method='HALF-UP') + return self.env['ir.qweb.field.float'].value_to_html(qty, {'decimal_precision': 'Product Unit of Measure'}) + def _get_usage(self, move_line): usage = '' if (move_line.location_id.usage == 'internal') and (move_line.location_dest_id.usage == 'internal'): @@ -114,9 +120,9 @@ class MrpStockReport(models.TransientModel): 'model_id': move_line.id, 'model': 'stock.move.line', 'product_id': move_line.product_id.display_name, + 'product_qty_uom': "%s %s" % (self._quantity_to_str(move_line.product_uom_id, move_line.product_id.uom_id, move_line.qty_done), move_line.product_id.uom_id.name), 'lot_name': move_line.lot_id.name, 'lot_id': move_line.lot_id.id, - 'product_qty_uom': str(move_line.product_uom_id._compute_quantity(move_line.qty_done, move_line.product_id.uom_id, rounding_method='HALF-UP')) + ' ' + move_line.product_id.uom_id.name, 'location_source': move_line.location_id.name if not unfoldable or level == 1 else False, 'location_destination': move_line.location_dest_id.name if not unfoldable or level == 1 else False, 'reference_id': ref, diff --git a/addons/stock/tests/test_move.py b/addons/stock/tests/test_move.py index 8e7eba5e5f7..d93d017feaa 100644 --- a/addons/stock/tests/test_move.py +++ b/addons/stock/tests/test_move.py @@ -1474,6 +1474,41 @@ class StockMove(TransactionCase): self.assertEqual(move1.state, 'confirmed') self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location, lot_id=lot1), 0.0) + def test_use_unreserved_move_line_3(self): + """ Test the behavior of `_free_reservation` when ran on a recordset of move lines where + some are assigned and some are force assigned. `_free_reservation` should not use an + already processed move line when looking for a move line candidate to unreserve. + """ + self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0) + + move1 = self.env['stock.move'].create({ + 'name': 'test_use_unreserved_move_line_3', + 'location_id': self.stock_location.id, + 'location_dest_id': self.customer_location.id, + 'product_id': self.product1.id, + 'product_uom': self.uom_unit.id, + 'product_uom_qty': 3.0, + }) + move1._action_confirm() + move1._action_assign() + move1.quantity_done = 1 + + # add a forced move line in `move1` + move1.write({'move_line_ids': [(0, 0, { + 'product_id': self.product1.id, + 'product_uom_id': self.uom_unit.id, + 'qty_done': 2, + 'product_uom_qty': 0, + 'lot_id': False, + 'package_id': False, + 'result_package_id': False, + 'location_id': move1.location_id.id, + 'location_dest_id': move1.location_dest_id.id, + })]}) + move1._action_done() + + self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.customer_location), 3.0) + def test_edit_reserved_move_line_1(self): """ Test that editing a stock move line linked to an untracked product correctly and directly adapts the reservation. In this case, we edit the sublocation where we take the diff --git a/addons/stock/tests/test_move2.py b/addons/stock/tests/test_move2.py index 4589fb12a56..834fd3ec3ba 100644 --- a/addons/stock/tests/test_move2.py +++ b/addons/stock/tests/test_move2.py @@ -1015,6 +1015,50 @@ class TestSinglePicking(TestStockCommon): self.assertEqual(sum(move1.move_line_ids.mapped('qty_done')), 2.0) self.assertEqual(move1.state, 'done') + def test_extra_move_4(self): + """ Create a picking with similar moves (created after + confirmation). Action done should propagate all the extra + quantity and only merge extra moves in their original moves. + """ + delivery = self.env['stock.picking'].create({ + 'location_id': self.stock_location, + 'location_dest_id': self.customer_location, + 'partner_id': self.partner_delta_id, + 'picking_type_id': self.picking_type_out, + }) + self.MoveObj.create({ + 'name': self.productA.name, + 'product_id': self.productA.id, + 'product_uom_qty': 5, + 'quantity_done': 10, + 'product_uom': self.productA.uom_id.id, + 'picking_id': delivery.id, + 'location_id': self.stock_location, + 'location_dest_id': self.customer_location, + }) + stock_location = self.env['stock.location'].browse(self.stock_location) + self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 5) + delivery.action_confirm() + delivery.action_assign() + + delivery.write({ + 'move_lines': [(0, 0, { + 'name': self.productA.name, + 'product_id': self.productA.id, + 'product_uom_qty': 0, + 'quantity_done': 10, + 'state': 'assigned', + 'product_uom': self.productA.uom_id.id, + 'picking_id': delivery.id, + 'location_id': self.stock_location, + 'location_dest_id': self.customer_location, + })] + }) + delivery.action_done() + self.assertEqual(len(delivery.move_lines), 2, 'Move should not be merged together') + for move in delivery.move_lines: + self.assertEqual(move.quantity_done, move.product_uom_qty, 'Initial demand should be equals to quantity done') + def test_recheck_availability_1(self): """ Check the good behavior of check availability. I create a DO for 2 unit with only one in stock. After the first check availability, I should have 1 reserved diff --git a/addons/stock/views/stock_picking_views.xml b/addons/stock/views/stock_picking_views.xml index 16bdbe44738..a0c8ab1c6a4 100644 --- a/addons/stock/views/stock_picking_views.xml +++ b/addons/stock/views/stock_picking_views.xml @@ -225,13 +225,14 @@
+

diff --git a/addons/web/static/src/js/views/calendar/calendar_model.js b/addons/web/static/src/js/views/calendar/calendar_model.js index 5542ab06476..6a2010708fc 100644 --- a/addons/web/static/src/js/views/calendar/calendar_model.js +++ b/addons/web/static/src/js/views/calendar/calendar_model.js @@ -632,7 +632,8 @@ return AbstractModel.extend({ var date_start; var date_stop; var date_delay = evt[this.mapping.date_delay] || 1.0, - all_day = this.mapping.all_day ? evt[this.mapping.all_day] : false, + all_day = this.fields[this.mapping.date_start].type === 'date' || + this.mapping.all_day && evt[this.mapping.all_day] || false, the_title = '', attendees = []; @@ -656,8 +657,6 @@ return AbstractModel.extend({ if (this.mapping.all_day && evt[this.mapping.all_day]) { date_stop.add(1, 'days'); } - var isAllDay = this.fields[this.mapping.date_start].type === 'date' || - this.mapping.all_day && evt[this.mapping.all_day] || false; var r = { 'record': evt, 'start': date_start, @@ -665,7 +664,7 @@ return AbstractModel.extend({ 'r_start': date_start, 'r_end': date_stop, 'title': the_title, - 'allDay': isAllDay, + 'allDay': all_day, 'id': evt.id, 'attendees':attendees, }; diff --git a/addons/web/static/tests/views/calendar_tests.js b/addons/web/static/tests/views/calendar_tests.js index 604b8b5bcee..ada068202c7 100644 --- a/addons/web/static/tests/views/calendar_tests.js +++ b/addons/web/static/tests/views/calendar_tests.js @@ -1595,7 +1595,7 @@ QUnit.module('Views', { }); QUnit.test('set event as all day when field is date', function (assert) { - assert.expect(1); + assert.expect(2); this.data.event.records[0].start_date = "2016-12-14"; @@ -1619,9 +1619,16 @@ QUnit.module('Views', { viewOptions: { initialDate: initialDate, }, + session: { + getTZOffset: function () { + return -480; + } + }, }); assert.strictEqual(calendar.$('.fc-day-grid .fc-event-container').length, 1, "should be one event in the all day row"); + assert.strictEqual(calendar.model.data.data[0].r_start.date(), 14, + "the date should be 14"); calendar.destroy(); }); diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py index c87d7226893..f25a38ca456 100644 --- a/addons/website/models/ir_http.py +++ b/addons/website/models/ir_http.py @@ -70,15 +70,15 @@ class Http(models.AbstractModel): @classmethod def _add_dispatch_parameters(cls, func): - context = dict(request.context) - if not context.get('tz'): + context = {} + if not request.context.get('tz'): context['tz'] = request.session.get('geoip', {}).get('time_zone') request.website = request.env['website'].get_current_website() # can use `request.env` since auth methods are called context['website_id'] = request.website.id - # bind modified context - request.context = context + # modify bound context + request.context = dict(request.context, **context) super(Http, cls)._add_dispatch_parameters(func) @@ -116,7 +116,7 @@ class Http(models.AbstractModel): mypage = pages[0] if pages else False _, ext = os.path.splitext(req_page) if mypage: - return request.render(mypage.view_id.id, { + return request.render(mypage.get_view_identifier(), { # 'path': req_page[1:], 'deletable': True, 'main_object': mypage, diff --git a/addons/website/models/website.py b/addons/website/models/website.py index f8e00eb4494..09dced17b03 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -658,6 +658,11 @@ class Page(models.Model): item = self.search_read(domain, fields=['id', 'name', 'url', 'website_published', 'website_indexed', 'date_publish', 'menu_ids', 'is_homepage'], limit=1) return item + @api.multi + def get_view_identifier(self): + """ Get identifier of this page view that may be used to render it """ + return self.view_id.id + @api.model def save_page_info(self, website_id, data): website = self.env['website'].browse(website_id) diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml index 04675ea315e..6dfe832cf1f 100644 --- a/addons/website_sale/views/templates.xml +++ b/addons/website_sale/views/templates.xml @@ -176,7 +176,7 @@ Shop
-
+
diff --git a/odoo/addons/base/models/ir_mail_server.py b/odoo/addons/base/models/ir_mail_server.py index d010d486f41..a0d08b6295f 100644 --- a/odoo/addons/base/models/ir_mail_server.py +++ b/odoo/addons/base/models/ir_mail_server.py @@ -422,6 +422,7 @@ class IrMailServer(models.Model): email_to = message['To'] email_cc = message['Cc'] email_bcc = message['Bcc'] + del message['Bcc'] smtp_to_list = [ address diff --git a/odoo/addons/base/models/ir_translation.py b/odoo/addons/base/models/ir_translation.py index c073db3656c..1cc17189323 100644 --- a/odoo/addons/base/models/ir_translation.py +++ b/odoo/addons/base/models/ir_translation.py @@ -458,13 +458,18 @@ class IrTranslation(models.Model): continue # remap existing translations on terms when possible + trans_src = record_trans.mapped('src') for trans in record_trans: if trans.src == trans.value: discarded += trans elif trans.src not in terms: matches = get_close_matches(trans.src, terms, 1, 0.9) if matches: - trans.write({'src': matches[0], 'state': trans.state}) + if matches[0] in trans_src: + # there is already a translation for this term; discard this one + discarded += trans + else: + trans.write({'src': matches[0], 'state': trans.state}) else: outdated += trans