diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 83407aec4bc..ae8dc1862b5 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -591,7 +591,8 @@ class AccountInvoice(models.Model): for invoice in self: # Delete non-manual tax lines self._cr.execute("DELETE FROM account_invoice_tax WHERE invoice_id=%s AND manual is False", (invoice.id,)) - self.invalidate_cache() + if self._cr.rowcount: + self.invalidate_cache() # Generate one tax line per tax, however many invoice lines it's applied to tax_grouped = invoice.get_taxes_values() diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index f8b46f43dc0..36bc522d5d5 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -55,7 +55,8 @@ class AccountMove(models.Model): total_amount += amount for partial_line in (line.matched_debit_ids + line.matched_credit_ids): total_reconciled += partial_line.amount - if float_is_zero(total_amount, precision_rounding=move.currency_id.rounding): + precision_currency = move.currency_id or move.company_id.currency_id + if float_is_zero(total_amount, precision_rounding=precision_currency.rounding): move.matched_percentage = 1.0 else: move.matched_percentage = total_reconciled / total_amount diff --git a/addons/l10n_multilang/models/l10n_multilang.py b/addons/l10n_multilang/models/l10n_multilang.py index 0c096c4cdf3..4bde8505d5c 100644 --- a/addons/l10n_multilang/models/l10n_multilang.py +++ b/addons/l10n_multilang/models/l10n_multilang.py @@ -32,14 +32,14 @@ class AccountChartTemplate(models.Model): for element in in_ids.with_context(lang=None): if value[element.id]: #copy Translation from Source to Destination object - xlat_obj.create({ - 'name': out_ids._name + ',' + in_field, - 'type': 'model', - 'res_id': out_ids[counter].id, - 'lang': lang, - 'src': element[in_field], - 'value': value[element.id], - }) + xlat_obj._set_ids( + out_ids._name + ',' + in_field, + 'model', + lang, + out_ids[counter].ids, + value[element.id], + element[in_field] + ) else: _logger.info('Language: %s. Translation from template: there is no translation available for %s!' % (lang, element[in_field])) counter += 1 diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index a37ee94e411..1b9c1185390 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -400,6 +400,9 @@ class SaleOrder(models.Model): precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') invoices = {} references = {} + invoices_origin = {} + invoices_name = {} + for order in self: group_key = order.id if grouped else (order.partner_invoice_id.id, order.currency_id.id) for line in order.order_line.sorted(key=lambda l: l.qty_to_invoice < 0): @@ -410,13 +413,14 @@ class SaleOrder(models.Model): invoice = inv_obj.create(inv_data) references[invoice] = order invoices[group_key] = invoice + invoices_origin[group_key] = [invoice.origin] + invoices_name[group_key] = [invoice.name] elif group_key in invoices: - vals = {} - if order.name not in invoices[group_key].origin.split(', '): - vals['origin'] = invoices[group_key].origin + ', ' + order.name - if order.client_order_ref and order.client_order_ref not in invoices[group_key].name.split(', ') and order.client_order_ref != invoices[group_key].name: - vals['name'] = invoices[group_key].name + ', ' + order.client_order_ref - invoices[group_key].write(vals) + if order.name not in invoices_origin[group_key]: + invoices_origin[group_key].append(order.name) + if order.client_order_ref and order.client_order_ref not in invoices_name[group_key]: + invoices_name[group_key].append(order.client_order_ref) + if line.qty_to_invoice > 0: line.invoice_line_create(invoices[group_key].id, line.qty_to_invoice) elif line.qty_to_invoice < 0 and final: @@ -426,6 +430,10 @@ class SaleOrder(models.Model): if order not in references[invoices[group_key]]: references[invoices[group_key]] |= order + for group_key in invoices: + invoices[group_key].write({'name': ', '.join(invoices_name[group_key]), + 'origin': ', '.join(invoices_origin[group_key])}) + if not invoices: raise UserError(_('There is no invoiceable line.')) diff --git a/addons/stock/models/stock_warehouse.py b/addons/stock/models/stock_warehouse.py index 9b5cebd9638..dd8d2a09d07 100644 --- a/addons/stock/models/stock_warehouse.py +++ b/addons/stock/models/stock_warehouse.py @@ -150,11 +150,14 @@ class Warehouse(models.Model): # If another partner assigned if vals.get('partner_id'): warehouses._update_partner_data(vals['partner_id'], vals.get('company_id')) + res = super(Warehouse, self).write(vals) # check if we need to delete and recreate route if vals.get('reception_steps') or vals.get('delivery_steps'): - warehouses._update_routes() + route_vals = warehouses._update_routes() + if route_vals: + self.write(route_vals) if vals.get('resupply_wh_ids') and not vals.get('resupply_route_ids'): for warehouse in warehouses: @@ -280,7 +283,7 @@ class Warehouse(models.Model): reception_route.pull_ids.write({'active': False}) reception_route.push_ids.write({'active': False}) else: - reception_route = self.env['stock.location.route'].create(warehouse._get_reception_delivery_route_values(warehouse.reception_steps)) + warehouse.reception_route_id = reception_route = self.env['stock.location.route'].create(warehouse._get_reception_delivery_route_values(warehouse.reception_steps)) # push / procurement (pull) rules for reception routings = routes_data[warehouse.id][warehouse.reception_steps] push_rules_list, pull_rules_list = warehouse._get_push_pull_rules_values( @@ -593,11 +596,18 @@ class Warehouse(models.Model): routes_data = self.get_routes_dict() # change the default source and destination location and (de)activate operation types self._update_picking_type() - self._create_or_update_delivery_route(routes_data) - self._create_or_update_reception_route(routes_data) - self._create_or_update_crossdock_route(routes_data) - self._create_or_update_mto_pull(routes_data) - return True + delivery_route = self._create_or_update_delivery_route(routes_data) + reception_route = self._create_or_update_reception_route(routes_data) + crossdock_route = self._create_or_update_crossdock_route(routes_data) + mto_pull = self._create_or_update_mto_pull(routes_data) + + return { + 'route_ids': [(4, route.id) for route in reception_route | delivery_route | crossdock_route], + 'mto_pull_id': mto_pull.id, + 'reception_route_id': reception_route.id, + 'delivery_route_id': delivery_route.id, + 'crossdock_route_id': crossdock_route.id, + } @api.one def _update_picking_type(self): diff --git a/odoo/netsvc.py b/odoo/netsvc.py index 7772d7d47cd..c8d9dbb6441 100644 --- a/odoo/netsvc.py +++ b/odoo/netsvc.py @@ -119,6 +119,13 @@ def init_logger(): if dirname and not os.path.isdir(dirname): os.makedirs(dirname) if tools.config['logrotate'] is not False: + if tools.config['workers'] > 1: + # TODO: fallback to regular file logging in master for safe(r) defaults? + # + # Doing so here would be a good idea but also might break + # situations were people do log-shipping of rotated data? + _logger.warn("WARNING: built-in log rotation is not reliable in multi-worker scenarios and may incur significant data loss. " + "It is strongly recommended to use an external log rotation utility or use system loggers (--syslog) instead.") handler = logging.handlers.TimedRotatingFileHandler(filename=logf, when='D', interval=1, backupCount=30) elif os.name == 'posix': handler = logging.handlers.WatchedFileHandler(logf) diff --git a/odoo/tools/mail.py b/odoo/tools/mail.py index ce1ccb90e3a..97a3e427a94 100644 --- a/odoo/tools/mail.py +++ b/odoo/tools/mail.py @@ -45,11 +45,12 @@ class _Cleaner(clean.Cleaner): _style_whitelist = [ 'font-size', 'font-family', 'font-weight', 'background-color', 'color', 'text-align', 'line-height', 'letter-spacing', 'text-transform', 'text-decoration', + 'float', 'vertical-align', 'padding', 'padding-top', 'padding-left', 'padding-bottom', 'padding-right', - 'margin', 'margin-top', 'margin-left', 'margin-bottom', 'margin-right' + 'margin', 'margin-top', 'margin-left', 'margin-bottom', 'margin-right', # box model - 'border', 'border-color', 'border-radius', 'border-style', 'height', - 'margin', 'padding', 'width', 'max-width', 'min-width', + 'border', 'border-color', 'border-radius', 'border-style', 'border-width', + 'height', 'margin', 'padding', 'width', 'max-width', 'min-width', # tables 'border-collapse', 'border-spacing', 'caption-side', 'empty-cells', 'table-layout']