diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 23b1a849809..db16d61e24e 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -39,7 +39,7 @@ class AccountInvoice(models.Model): _order = "date_invoice desc, number desc, id desc" @api.one - @api.depends('invoice_line_ids.price_subtotal', 'tax_line_ids.amount', 'currency_id', 'company_id') + @api.depends('invoice_line_ids.price_subtotal', 'tax_line_ids.amount', 'currency_id', 'company_id', 'date_invoice') def _compute_amount(self): self.amount_untaxed = sum(line.price_subtotal for line in self.invoice_line_ids) self.amount_tax = sum(line.amount for line in self.tax_line_ids) @@ -47,8 +47,9 @@ class AccountInvoice(models.Model): amount_total_company_signed = self.amount_total amount_untaxed_signed = self.amount_untaxed if self.currency_id and self.currency_id != self.company_id.currency_id: - amount_total_company_signed = self.currency_id.compute(self.amount_total, self.company_id.currency_id) - amount_untaxed_signed = self.currency_id.compute(self.amount_untaxed, self.company_id.currency_id) + currency_id = self.currency_id.with_context(date=self.date_invoice) + amount_total_company_signed = currency_id.compute(self.amount_total, self.company_id.currency_id) + amount_untaxed_signed = currency_id.compute(self.amount_untaxed, self.company_id.currency_id) sign = self.type in ['in_refund', 'out_refund'] and -1 or 1 self.amount_total_company_signed = amount_total_company_signed * sign self.amount_total_signed = self.amount_total * sign diff --git a/addons/account/views/report_agedpartnerbalance.xml b/addons/account/views/report_agedpartnerbalance.xml index 8681931febb..8fa10c8df90 100644 --- a/addons/account/views/report_agedpartnerbalance.xml +++ b/addons/account/views/report_agedpartnerbalance.xml @@ -4,7 +4,7 @@
-

Aged Trial Balance

+

Aged Partner Balance

diff --git a/addons/account_asset/models/account_invoice.py b/addons/account_asset/models/account_invoice.py index 9eb4cca484b..91e6336d23c 100644 --- a/addons/account_asset/models/account_invoice.py +++ b/addons/account_asset/models/account_invoice.py @@ -28,8 +28,8 @@ class AccountInvoiceLine(models.Model): _inherit = 'account.invoice.line' asset_category_id = fields.Many2one('account.asset.category', string='Asset Category') - asset_start_date = fields.Date(string='Asset End Date', compute='_get_asset_date', readonly=True, store=True) - asset_end_date = fields.Date(string='Asset Start Date', compute='_get_asset_date', readonly=True, store=True) + asset_start_date = fields.Date(string='Asset Start Date', compute='_get_asset_date', readonly=True, store=True) + asset_end_date = fields.Date(string='Asset End Date', compute='_get_asset_date', readonly=True, store=True) asset_mrr = fields.Float(string='Monthly Recurring Revenue', compute='_get_asset_date', readonly=True, digits=dp.get_precision('Account'), store=True) @api.one diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py index bd65166fd65..d3e1a71555b 100644 --- a/addons/calendar/models/calendar.py +++ b/addons/calendar/models/calendar.py @@ -916,10 +916,10 @@ class Meeting(models.Model): meeting_attendees |= attendee meeting_partners |= partner - if current_user.email != partner.email: - attendee._send_mail_to_attendees('calendar.calendar_template_meeting_invitation') - if meeting_attendees: + to_notify = meeting_attendees.filtered(lambda a: a.email != current_user.email) + to_notify._send_mail_to_attendees('calendar.calendar_template_meeting_invitation') + meeting.write({'attendee_ids': [(4, meeting_attendee.id) for meeting_attendee in meeting_attendees]}) if meeting_partners: meeting.message_subscribe(partner_ids=meeting_partners.ids) diff --git a/addons/event/models/event_mail.py b/addons/event/models/event_mail.py index a17635efd7d..6a5edd12854 100644 --- a/addons/event/models/event_mail.py +++ b/addons/event/models/event_mail.py @@ -71,7 +71,8 @@ class EventMailScheduler(models.Model): if self.interval_type == 'after_sub': # update registration lines lines = [] - for registration in filter(lambda item: item not in [mail_reg.registration_id for mail_reg in self.mail_registration_ids], self.event_id.registration_ids): + reg_ids = [mail_reg.registration_id for mail_reg in self.mail_registration_ids] + for registration in filter(lambda item: item not in reg_ids, self.event_id.registration_ids): lines.append((0, 0, {'registration_id': registration.id})) if lines: self.write({'mail_registration_ids': lines}) diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py index 84886443a37..a6b1364308d 100644 --- a/addons/hr_expense/models/hr_expense.py +++ b/addons/hr_expense/models/hr_expense.py @@ -254,7 +254,7 @@ class HrExpense(models.Model): # Calculate tax lines and adjust base line taxes = expense.tax_ids.compute_all(expense.unit_amount, expense.currency_id, expense.quantity, expense.product_id) account_move[-1]['price'] = taxes['total_excluded'] - account_move[-1]['tax_ids'] = expense.tax_ids.id + account_move[-1]['tax_ids'] = expense.tax_ids.ids for tax in taxes['taxes']: account_move.append({ 'type': 'tax', diff --git a/addons/l10n_de_skr03/data/l10n_de_skr03_chart_data.xml b/addons/l10n_de_skr03/data/l10n_de_skr03_chart_data.xml index 368cd9a5937..d725d573070 100644 --- a/addons/l10n_de_skr03/data/l10n_de_skr03_chart_data.xml +++ b/addons/l10n_de_skr03/data/l10n_de_skr03_chart_data.xml @@ -484,7 +484,7 @@ Postbank 2 1120 - + @@ -708,7 +708,7 @@ Nebenkasse 1 1010 - + @@ -750,14 +750,14 @@ Postbank 3 1130 - + Bank 3 1230 - + @@ -806,7 +806,7 @@ Schecks 1330 - + @@ -925,7 +925,7 @@ Bank 4 1240 - + @@ -1492,7 +1492,7 @@ LZB-Guthaben 1190 - + @@ -2850,7 +2850,7 @@ Bank 2 1220 - + @@ -3116,7 +3116,7 @@ Postbank 1 1110 - + @@ -4019,7 +4019,7 @@ Nebenkasse 2 1020 - + @@ -4796,7 +4796,7 @@ Bank 1 1210 - + @@ -5020,7 +5020,7 @@ Postbank 1100 - + @@ -6161,7 +6161,7 @@ Bank 5 1250 - + @@ -6511,7 +6511,7 @@ Bundesbankguthaben 1195 - + @@ -7218,7 +7218,7 @@ Geldtransit 1360 - + diff --git a/addons/l10n_de_skr04/data/l10n_de_skr04_chart_data.xml b/addons/l10n_de_skr04/data/l10n_de_skr04_chart_data.xml index 81b0d42574f..fb9f9bf758d 100644 --- a/addons/l10n_de_skr04/data/l10n_de_skr04_chart_data.xml +++ b/addons/l10n_de_skr04/data/l10n_de_skr04_chart_data.xml @@ -3463,7 +3463,7 @@ 1460 Geldtransit - + @@ -3615,7 +3615,7 @@ 1610 Nebenkasse 1 - + @@ -3623,7 +3623,7 @@ 1620 Nebenkasse 2 - + @@ -3631,7 +3631,7 @@ 1700 Postbank - + @@ -3639,7 +3639,7 @@ 1710 Postbank 1 - + @@ -3647,7 +3647,7 @@ 1720 Postbank 2 - + @@ -3655,7 +3655,7 @@ 1730 Postbank 3 - + @@ -3679,7 +3679,7 @@ 1810 Bank 1 - + @@ -3687,7 +3687,7 @@ 1820 Bank 2 - + @@ -3695,7 +3695,7 @@ 1830 Bank 3 - + @@ -3703,7 +3703,7 @@ 1840 Bank 4 - + @@ -3711,7 +3711,7 @@ 1850 Bank 5 - + diff --git a/addons/l10n_fr/data/account_fiscal_position_template_data.xml b/addons/l10n_fr/data/account_fiscal_position_template_data.xml index 68be1323c2c..51108bb0be8 100644 --- a/addons/l10n_fr/data/account_fiscal_position_template_data.xml +++ b/addons/l10n_fr/data/account_fiscal_position_template_data.xml @@ -38,30 +38,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + @@ -127,30 +152,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/l10n_fr/data/account_tax_data.xml b/addons/l10n_fr/data/account_tax_data.xml index 7778e6ef82d..1d14f61cae1 100644 --- a/addons/l10n_fr/data/account_tax_data.xml +++ b/addons/l10n_fr/data/account_tax_data.xml @@ -32,6 +32,31 @@ taxes + + 20.0-TTC + taxes + + + + 10.0-TTC + taxes + + + + 8.5-TTC + taxes + + + + 5.5-TTC + taxes + + + + 2.1-TTC + taxes + + ACH-8_5 taxes @@ -249,6 +274,77 @@ + + + + TVA collectée (vente) 20,0% TTC + 20.0-TTC + + + percent + + + + sale + + + + + + TVA collectée (vente) 8,5% TTC + 8.5-TTC + + + percent + + + + sale + + + + + + TVA collectée (vente) 10,0% TTC + 10.0-TTC + + + percent + + + + sale + + + + + + TVA collectée (vente) 5,5% TTC + 5.5-TTC + + + percent + + + + sale + + + + + + TVA collectée (vente) 2,1% TTC + 2.1-TTC + + + percent + + + + sale + + + diff --git a/addons/mail/models/mail_template.py b/addons/mail/models/mail_template.py index 8508fa9f250..d87cad5fec7 100644 --- a/addons/mail/models/mail_template.py +++ b/addons/mail/models/mail_template.py @@ -488,7 +488,7 @@ class MailTemplate(models.Model): ) # Add report in attachments: generate once for all template_res_ids - if template.report_template and not 'report_template_in_attachment' in self.env.context: + if template.report_template: for res_id in template_res_ids: attachments = [] report_name = self.render_template(template.report_name, template.model, res_id) diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index 7363bf6510b..6af6a2206f4 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -1692,12 +1692,14 @@ class MailThread(models.AbstractModel): m2m_attachment_ids += [(4, id) for id in attachment_ids] # Handle attachments parameter, that is a dictionary of attachments for attachment in attachments: + cid = False if len(attachment) == 2: name, content = attachment elif len(attachment) == 3: name, content, info = attachment if info and info.get('cid'): - cid_mapping[info['cid']] = name + cid = info['cid'] + cid_mapping[cid] = name else: continue if isinstance(content, unicode): @@ -1705,7 +1707,7 @@ class MailThread(models.AbstractModel): data_attach = { 'name': name, 'datas': base64.b64encode(str(content)), - 'datas_fname': name, + 'datas_fname': cid or name, 'description': name, 'res_model': message_data['model'], 'res_id': message_data['res_id'], @@ -1718,8 +1720,11 @@ class MailThread(models.AbstractModel): postprocessed = False for node in root.iter('img'): if node.get('src', '').startswith('cid:'): - fname = cid_mapping.get(node.get('src').split('cid:')[1], node.get('data-filename', '')) - attachment = parameter_attachments.filtered(lambda attachment: attachment.name == fname) + cid = node.get('src').split('cid:')[1] + fname = cid_mapping.get(cid, node.get('data-filename', '')) + attachment = parameter_attachments.filtered(lambda attachment: attachment.datas_fname == cid) + if not attachment: + attachment = parameter_attachments.filtered(lambda attachment: attachment.datas_fname == fname) if attachment: node.set('src', '/web/image/%s' % attachment.ids[0]) postprocessed = True diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index acaf1825348..9cf1c22e482 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -192,7 +192,7 @@ class MailComposer(models.TransientModel): @api.multi def send_mail_action(self): # TDE/ ??? - return self.with_context(report_template_in_attachment=True).send_mail() + return self.send_mail() @api.multi def send_mail(self, auto_commit=False): @@ -358,20 +358,6 @@ class MailComposer(models.TransientModel): if template.user_signature and 'body_html' in values: signature = self.env.user.signature values['body_html'] = tools.append_content_to_html(values['body_html'], signature, plaintext=False) - if template.report_template: - attachment = self.env['ir.attachment'] - attach = self.generate_attachment_from_report(template_id, res_id) - for attach_fname, attach_datas in attach[res_id].pop('attachments', []): - data_attach = { - 'name': attach_fname, - 'datas': attach_datas, - 'datas_fname': attach_fname, - 'res_model': 'mail.compose.message', - 'res_id': 0, - 'type': 'binary', - } - values.setdefault('attachment_ids', list()).append(attachment.create(data_attach).id) - elif template_id: values = self.generate_email_for_composer(template_id, [res_id])[res_id] # transform attachments into attachment_ids; not attached to the document because this will @@ -402,13 +388,6 @@ class MailComposer(models.TransientModel): return {'value': values} - @api.multi - def generate_attachment_from_report(self, template_id, res_id): - fields = ['attachment_ids'] - result = self.env['mail.template'].with_context(tpl_partners_only=True).browse(template_id).generate_email([res_id], fields=fields) - return result - - @api.multi def save_as_template(self): """ hit save as template button: current form value will be a new diff --git a/addons/mrp/models/mrp_bom.py b/addons/mrp/models/mrp_bom.py index 0fbc3c030e1..3feb7b498be 100644 --- a/addons/mrp/models/mrp_bom.py +++ b/addons/mrp/models/mrp_bom.py @@ -111,6 +111,11 @@ class MrpBom(models.Model): return self.search(domain, order='sequence, product_id', limit=1) def explode(self, product, quantity, picking_type=False): + """ + Explodes the BoM and creates two lists with all the information you need: bom_done and line_done + Quantity describes the number of times you need the BoM: so the quantity divided by the number created by the BoM + and converted into its UoM + """ boms_done = [(self, {'qty': quantity, 'product': product, 'original_qty': quantity, 'parent_line': False})] lines_done = [] templates_done = product.product_tmpl_id @@ -125,11 +130,11 @@ class MrpBom(models.Model): if current_line.product_id.product_tmpl_id in templates_done: raise UserError(_('Recursion error! A product with a Bill of Material should not have itself in its BoM or child BoMs!')) - line_quantity = current_qty * current_line.product_qty / current_line.bom_id.product_qty + line_quantity = current_qty * current_line.product_qty bom = self._bom_find(product=current_line.product_id, picking_type=picking_type or self.picking_type_id, company_id=self.company_id.id) if bom.type == 'phantom': - converted_line_quantity = current_line.product_uom_id._compute_quantity(line_quantity, bom.product_uom_id) - bom_lines = [(line, current_line.product_id, line_quantity, current_line) for line in bom.bom_line_ids] + bom_lines + converted_line_quantity = current_line.product_uom_id._compute_quantity(line_quantity / bom.product_qty, bom.product_uom_id) + bom_lines = [(line, current_line.product_id, converted_line_quantity, current_line) for line in bom.bom_line_ids] + bom_lines templates_done |= current_line.product_id.product_tmpl_id boms_done.append((bom, {'qty': converted_line_quantity, 'product': current_product, 'original_qty': quantity, 'parent_line': current_line})) else: diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index 7d6bc5d881b..d0f5a2f00f4 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -55,7 +55,7 @@ class MrpProduction(models.Model): states={'confirmed': [('readonly', False)]}) product_tmpl_id = fields.Many2one('product.template', 'Product Template', related='product_id.product_tmpl_id') product_qty = fields.Float( - 'Quantity to Produce', + 'Quantity To Produce', default=1.0, digits=dp.get_precision('Product Unit of Measure'), readonly=True, required=True, states={'confirmed': [('readonly', False)]}) @@ -79,12 +79,12 @@ class MrpProduction(models.Model): states={'confirmed': [('readonly', False)]}, help="Location where the system will stock the finished products.") date_planned_start = fields.Datetime( - 'Expected Start Date', copy=False, default=fields.Datetime.now, - index=True, required=True, readonly=True, + 'Deadline Start', copy=False, default=fields.Datetime.now, + index=True, required=True, states={'confirmed': [('readonly', False)]}, oldname="date_planned") date_planned_finished = fields.Datetime( - 'Expected End Date', copy=False, default=fields.Datetime.now, - index=True, readonly=True, + 'Deadline End', copy=False, default=fields.Datetime.now, + index=True, states={'confirmed': [('readonly', False)]}) date_start = fields.Datetime('Start Date', copy=False, index=True, readonly=True) date_finished = fields.Datetime('End Date', copy=False, index=True, readonly=True) @@ -264,7 +264,7 @@ class MrpProduction(models.Model): def _generate_moves(self): for production in self: production._generate_finished_moves() - factor = production.product_uom_id._compute_quantity(production.product_qty, production.bom_id.product_uom_id) + factor = production.product_uom_id._compute_quantity(production.product_qty, production.bom_id.product_uom_id) / production.bom_id.product_qty boms, lines = production.bom_id.explode(production.product_id, factor, picking_type=production.bom_id.picking_type_id) production._generate_raw_moves(lines) # Check for all draft moves whether they are mto or not @@ -311,8 +311,7 @@ class MrpProduction(models.Model): source_location = self.bom_id.routing_id.location_id else: source_location = self.location_src_id - original_quantity = self.product_uom_id._compute_quantity(self.product_qty, self.bom_id.product_uom_id) - + original_quantity = self.product_qty - self.qty_produced data = { 'name': self.name, 'date': self.date_planned_start, @@ -354,14 +353,21 @@ class MrpProduction(models.Model): move.procure_method = 'make_to_order' @api.multi - def _update_raw_move(self, bom_line, quantity, **kw): + def _update_raw_move(self, bom_line, line_data): + quantity = line_data['qty'] self.ensure_one() move = self.move_raw_ids.filtered(lambda x: x.bom_line_id.id == bom_line.id and x.state not in ('done', 'cancel')) if move: - move.write({'product_uom_qty': quantity}) + if quantity > 0: + move[0].write({'product_uom_qty': quantity}) + else: + if move[0].quantity_done > 0: + raise UserError(_('Lines need to be deleted, but can not as you still have some quantities to consume in them. ')) + move[0].action_cancel() + move[0].unlink() return move else: - self._generate_raw_move(bom_line, quantity) + self._generate_raw_move(bom_line, line_data) @api.multi def action_assign(self): @@ -381,7 +387,7 @@ class MrpProduction(models.Model): """ Create work orders. And probably do stuff, like things. """ orders_to_plan = self.filtered(lambda order: order.routing_id and order.state == 'confirmed') for order in orders_to_plan: - quantity = order.product_uom_id._compute_quantity(order.product_qty, order.bom_id.product_uom_id) + quantity = order.product_uom_id._compute_quantity(order.product_qty, order.bom_id.product_uom_id) / order.bom_id.product_qty boms, lines = order.bom_id.explode(order.product_id, quantity, picking_type=order.bom_id.picking_type_id) order._generate_workorders(boms) orders_to_plan.write({'state': 'planned'}) @@ -401,19 +407,18 @@ class MrpProduction(models.Model): BoMs """ workorders = self.env['mrp.workorder'] - qty = bom_data['qty'] + bom_qty = bom_data['qty'] - use_serial = any(product.tracking == 'serial' for product in self.mapped('move_finished_ids.product_id')) - if use_serial: + # Initial qty producing + if self.product_id.tracking == 'serial': quantity = 1.0 else: quantity = self.product_qty - sum(self.move_finished_ids.mapped('quantity_done')) quantity = quantity if (quantity > 0) else 0 - # TDE FIXME: what is qty compared to quantity ?? for operation in bom.routing_id.operation_ids: # create workorder - cycle_number = math.ceil(qty / bom.product_qty / operation.workcenter_id.capacity) # TODO: float_round UP + cycle_number = math.ceil(bom_qty / operation.workcenter_id.capacity) # TODO: float_round UP duration_expected = (operation.workcenter_id.time_start + operation.workcenter_id.time_stop + cycle_number * operation.time_cycle * 100.0 / operation.workcenter_id.time_efficiency) @@ -513,7 +518,7 @@ class MrpProduction(models.Model): self.post_inventory() moves_to_cancel = (self.move_raw_ids | self.move_finished_ids).filtered(lambda x: x.state not in ('done', 'cancel')) moves_to_cancel.action_cancel() - self.write({'state': 'done', 'date_finished': fields.datetime.now()}) + self.write({'state': 'done', 'date_finished': fields.Datetime.now()}) self.env["procurement.order"].search([('production_id', 'in', self.ids)]).check() self.write({'state': 'done'}) diff --git a/addons/mrp/models/mrp_routing.py b/addons/mrp/models/mrp_routing.py index b9effeadd8d..832b0328e89 100644 --- a/addons/mrp/models/mrp_routing.py +++ b/addons/mrp/models/mrp_routing.py @@ -69,6 +69,7 @@ class MrpRoutingWorkcenter(models.Model): batch = fields.Selection([ ('no', 'Once all products are processed'), ('yes', 'Once a minimum number of products is processed')], string='Next Operation', + help="""Will determine if the next work order will be planned after the previous one or after the first Quantity To Process of the previous one.""", default='no', required=True) batch_size = fields.Float('Quantity to Process', default=1.0) workorder_ids = fields.One2many('mrp.workorder', 'operation_id', string="Work Orders") diff --git a/addons/mrp/models/mrp_unbuild.py b/addons/mrp/models/mrp_unbuild.py index 0abd787fbf0..7254101505e 100644 --- a/addons/mrp/models/mrp_unbuild.py +++ b/addons/mrp/models/mrp_unbuild.py @@ -154,8 +154,8 @@ class MrpUnbuild(models.Model): def _generate_produce_moves(self): moves = self.env['stock.move'] for unbuild in self: - factor = unbuild.product_uom_id._compute_quantity(unbuild.product_qty, unbuild.bom_id.product_uom_id) - boms, lines = unbuild.bom_id.explode(unbuild.product_id, factor / unbuild.bom_id.product_qty, picking_type=unbuild.bom_id.picking_type_id) + factor = unbuild.product_uom_id._compute_quantity(unbuild.product_qty, unbuild.bom_id.product_uom_id) / unbuild.bom_id.product_qty + boms, lines = unbuild.bom_id.explode(unbuild.product_id, factor, picking_type=unbuild.bom_id.picking_type_id) for line, line_data in lines: moves += unbuild._generate_move_from_bom_line(line, line_data['qty']) return moves diff --git a/addons/mrp/models/mrp_workcenter.py b/addons/mrp/models/mrp_workcenter.py index 161c18e6473..b47294465ce 100644 --- a/addons/mrp/models/mrp_workcenter.py +++ b/addons/mrp/models/mrp_workcenter.py @@ -42,10 +42,10 @@ class MrpWorkcenter(models.Model): ('done', 'In Progress')], 'Status', compute="_compute_working_state", store=True) blocked_time = fields.Float( 'Blocked Time', compute='_compute_blocked_time', - help='Blocked hours over the last month') + help='Blocked hour(s) over the last month', digits=(16, 2)) productive_time = fields.Float( 'Productive Time', compute='_compute_productive_time', - help='Productive hours over the last month') + help='Productive hour(s) over the last month', digits=(16, 2)) oee = fields.Float(compute='_compute_oee', help='Overall Equipment Effectiveness, based on the last month') oee_target = fields.Float(string='OEE Target', help="OEE Target in percentage", default=90) performance = fields.Integer('Performance', compute='_compute_performance', help='Performance over the last month') @@ -99,7 +99,7 @@ class MrpWorkcenter(models.Model): ['duration', 'workcenter_id'], ['workcenter_id'], lazy=False) count_data = dict((item['workcenter_id'][0], item['duration']) for item in data) for workcenter in self: - workcenter.blocked_time = count_data.get(workcenter.id, 0.0) + workcenter.blocked_time = count_data.get(workcenter.id, 0.0) / 60.0 @api.multi def _compute_productive_time(self): @@ -112,15 +112,15 @@ class MrpWorkcenter(models.Model): ['duration', 'workcenter_id'], ['workcenter_id'], lazy=False) count_data = dict((item['workcenter_id'][0], item['duration']) for item in data) for workcenter in self: - workcenter.productive_time = count_data.get(workcenter.id, 0.0) + workcenter.productive_time = count_data.get(workcenter.id, 0.0) / 60.0 @api.depends('blocked_time', 'productive_time') - @api.one def _compute_oee(self): - if self.productive_time: - self.oee = round(self.productive_time * 100.0 / (self.productive_time + self.blocked_time), 2) - else: - self.oee = 0.0 + for order in self: + if order.productive_time: + order.oee = round(order.productive_time * 100.0 / (order.productive_time + order.blocked_time), 2) + else: + order.oee = 0.0 @api.multi def _compute_performance(self): @@ -185,7 +185,7 @@ class MrpWorkcenterProductivity(models.Model): loss_type = fields.Selection( "Effectiveness", related='loss_id.loss_type', store=True) description = fields.Text('Description') - date_start = fields.Datetime('Start Date', default=fields.Datetime.now(), required=True) + date_start = fields.Datetime('Start Date', default=fields.Datetime.now, required=True) date_end = fields.Datetime('End Date') duration = fields.Float('Duration', compute='_compute_duration', store=True) diff --git a/addons/mrp/models/mrp_workorder.py b/addons/mrp/models/mrp_workorder.py index 61a4ccbf21e..920d8b0bc4c 100644 --- a/addons/mrp/models/mrp_workorder.py +++ b/addons/mrp/models/mrp_workorder.py @@ -214,11 +214,15 @@ class MrpWorkorder(models.Model): for move_lot in move_lots: if qty_todo <= 0: break - if move_lot.quantity_done == 0 and qty_todo > move_lot.quantity: + if not move_lot.lot_id and qty_todo >= move_lot.quantity: qty_todo = qty_todo - move_lot.quantity self.active_move_lot_ids -= move_lot # Difference operator else: move_lot.quantity = move_lot.quantity - qty_todo + if move_lot.quantity_done - qty_todo > 0: + move_lot.quantity_done = move_lot.quantity_done - qty_todo + else: + move_lot.quantity_done = 0 qty_todo = 0 @api.multi diff --git a/addons/mrp/models/product.py b/addons/mrp/models/product.py index b4e46fda938..c3f457adb56 100644 --- a/addons/mrp/models/product.py +++ b/addons/mrp/models/product.py @@ -29,11 +29,8 @@ class ProductTemplate(models.Model): def action_view_mos(self): product_ids = self.mapped('product_variant_ids').ids action = self.env.ref('mrp.act_product_mrp_production').read()[0] - if len(self) == 1 and len(product_ids) == 1: - action['context'] = {'default_product_id': product_ids[0], 'search_default_product_id': product_ids[0]} - else: - action['domain'] = [('product_id', 'in', product_ids)] - action['context'] = {} + action['domain'] = [('product_id', 'in', product_ids)] + action['context'] = {} return action diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py index e3dd9e07994..5c539195444 100644 --- a/addons/mrp/models/stock_move.py +++ b/addons/mrp/models/stock_move.py @@ -164,6 +164,48 @@ class StockMove(models.Model): lots.create(vals) return True + @api.multi + def _create_extra_move(self): + ''' Creates an extra move if necessary depending on extra quantities than foreseen or extra moves''' + self.ensure_one() + quantity_to_split = 0 + extra_move = self.env['stock.move'] + rounding = self.product_uom.rounding + link_procurement = False + if self.procurement_id and self.production_id and float_compare(self.production_id.qty_produced, self.procurement_id.product_qty, precision_rounding=rounding) > 0: + done_moves_total = sum(self.production_id.move_finished_ids.filtered(lambda x: x.product_id == self.product_id and x.state == 'done').mapped('product_uom_qty')) + if float_compare(done_moves_total, self.procurement_id.product_qty, precision_rounding=rounding) >= 0: + quantity_to_split = 0 + self.product_uom_qty = self.quantity_done #TODO: could change qty on move_dest_id also (in case of 2-step in/out) + else: + quantity_to_split = done_moves_total + self.quantity_done - self.procurement_id.product_qty + elif float_compare(self.quantity_done, self.product_uom_qty, precision_rounding=rounding) > 0: + quantity_to_split = self.quantity_done - self.product_uom_qty + link_procurement = True + if quantity_to_split: + extra_move = self.copy(default={'quantity_done': quantity_to_split, 'product_uom_qty': quantity_to_split, 'production_id': self.production_id.id, + 'raw_material_production_id': self.raw_material_production_id.id, + 'procurement_id': link_procurement and self.procurement_id.id or False}) + extra_move.action_confirm() + if self.has_tracking != 'none': + qty_todo = self.quantity_done - quantity_to_split + for movelot in self.move_lot_ids.filtered(lambda x: x.done_wo): + if movelot.quantity_done and movelot.done_wo: + if float_compare(qty_todo, movelot.quantity_done, precision_rounding=rounding) >= 0: + qty_todo -= movelot.quantity_done + elif float_compare(qty_todo, 0, precision_rounding=rounding) > 0: + #split + remaining = movelot.quantity_done - qty_todo + movelot.quantity_done = qty_todo + movelot.copy(default={'move_id': extra_move.id, 'quantity_done': remaining}) + qty_todo = 0 + else: + movelot.move_id = extra_move.id + else: + self.quantity_done = self.product_uom_qty + self.product_uom_qty = self.quantity_done - quantity_to_split + return extra_move + @api.multi def move_validate(self): ''' Validate moves based on a production order. ''' @@ -171,25 +213,22 @@ class StockMove(models.Model): quant_obj = self.env['stock.quant'] moves_todo = self.env['stock.move'] moves_to_unreserve = self.env['stock.move'] + # Create extra moves where necessary for move in moves: rounding = move.product_uom.rounding if float_compare(move.quantity_done, 0.0, precision_rounding=rounding) <= 0: continue moves_todo |= move - if float_compare(move.quantity_done, move.product_uom_qty, precision_rounding=rounding) > 0: - remaining_qty = move.quantity_done - move.product_uom_qty # In UoM of move - extra_move = move.copy(default={'quantity_done': remaining_qty, 'product_uom_qty': remaining_qty, 'production_id': move.production_id.id, - 'raw_material_production_id': move.raw_material_production_id.id}) - move.quantity_done = move.product_uom_qty - extra_move.action_confirm() - moves_todo |= extra_move + moves_todo |= move._create_extra_move() + # Split moves where necessary and move quants for move in moves_todo: - if float_compare(move.quantity_done, move.product_uom_qty, precision_rounding=rounding): + rounding = move.product_uom.rounding + if float_compare(move.quantity_done, move.product_uom_qty, precision_rounding=rounding) < 0: # Need to do some kind of conversion here qty_split = move.product_uom._compute_quantity(move.product_uom_qty - move.quantity_done, move.product_id.uom_id) new_move = move.split(qty_split) # If you were already putting stock.move.lots on the next one in the work order, transfer those to the new move - move.move_lot_ids.filtered(lambda x: not x.done_wo).write({'move_id': new_move}) + move.move_lot_ids.filtered(lambda x: not x.done_wo or x.quantity_done == 0.0).write({'move_id': new_move}) self.browse(new_move).quantity_done = 0.0 main_domain = [('qty', '>', 0)] preferred_domain = [('reservation_id', '=', move.id)] diff --git a/addons/mrp/models/stock_scrap.py b/addons/mrp/models/stock_scrap.py index b78b896c8f0..b672176636b 100644 --- a/addons/mrp/models/stock_scrap.py +++ b/addons/mrp/models/stock_scrap.py @@ -41,6 +41,7 @@ class StockScrap(models.Model): def _prepare_move_values(self): vals = super(StockScrap, self)._prepare_move_values() if self.production_id: + vals['origin'] = vals['origin'] or self.production_id.name if self.product_id in self.production_id.move_finished_ids.mapped('product_id'): vals.update({'production_id': self.production_id.id}) else: diff --git a/addons/mrp/views/mrp_bom_views.xml b/addons/mrp/views/mrp_bom_views.xml index 07b851d168d..45e7d7cc842 100644 --- a/addons/mrp/views/mrp_bom_views.xml +++ b/addons/mrp/views/mrp_bom_views.xml @@ -48,7 +48,7 @@ - + @@ -78,6 +78,7 @@ + diff --git a/addons/mrp/views/mrp_production_views.xml b/addons/mrp/views/mrp_production_views.xml index ed1284cf2c8..003e703818a 100644 --- a/addons/mrp/views/mrp_production_views.xml +++ b/addons/mrp/views/mrp_production_views.xml @@ -48,8 +48,8 @@
-
- - + + diff --git a/addons/web/static/src/js/views/form_relational_widgets.js b/addons/web/static/src/js/views/form_relational_widgets.js index 4ce7ed6dbf3..dd4c4fb0fb6 100644 --- a/addons/web/static/src/js/views/form_relational_widgets.js +++ b/addons/web/static/src/js/views/form_relational_widgets.js @@ -955,7 +955,7 @@ var X2ManyListView = ListView.extend({ field.no_rerender = true; current_values[field.name] = field.get('value'); }); - var cached_records = _.filter(this.dataset.cache, function(item){return !_.isEmpty(item.values);}); + var cached_records = _.filter(this.dataset.cache, function(item){return !_.isEmpty(item.values) && !item.to_delete;}); var valid = _.every(cached_records, function(record){ _.each(fields, function(field){ var value = record.values[field.name]; diff --git a/addons/web/static/src/less/webclient_layout.less b/addons/web/static/src/less/webclient_layout.less index 3355f4f791f..034f00ad3d9 100644 --- a/addons/web/static/src/less/webclient_layout.less +++ b/addons/web/static/src/less/webclient_layout.less @@ -34,6 +34,7 @@ .o-flex(1, 1, auto); .o-flex-display(); min-height: 0; // Allow flex to shrink this element as much as it wants + height: 100%; .o_sub_menu { .o-flex(0, 0, @odoo-submenu-width); diff --git a/addons/web_editor/static/src/js/snippets.options.js b/addons/web_editor/static/src/js/snippets.options.js index 6d4800cd0db..4d533cadd00 100644 --- a/addons/web_editor/static/src/js/snippets.options.js +++ b/addons/web_editor/static/src/js/snippets.options.js @@ -609,7 +609,7 @@ odoo.define('web_editor.snippets.options', function (require) { var _editor = new widget.MediaDialog(null, {}, null, $image[0]).open(); _editor.$('[href="#editor-media-video"], [href="#editor-media-icon"]').addClass('hidden'); - _editor.on('saved', this, function () { + _editor.on('save', this, function () { var value = $image.attr("src"); this.background(type, value, $li); this.$target.addClass("oe_custom_bg"); diff --git a/addons/web_editor/static/src/js/widgets.js b/addons/web_editor/static/src/js/widgets.js index 00a693d40f2..7a3ab09dd76 100644 --- a/addons/web_editor/static/src/js/widgets.js +++ b/addons/web_editor/static/src/js/widgets.js @@ -34,11 +34,11 @@ Dialog = Dialog.extend({ self.$('input:first').focus(); }); this.on("closed", this, function () { - this.trigger(this.destroyAction); + this.trigger(this.destroyAction, this.final_data || null); }); }, save: function () { - this.destroyAction = "saved"; + this.destroyAction = "save"; this.close(); }, }); @@ -164,8 +164,8 @@ var MediaDialog = Dialog.extend({ }, save: function () { if (this.options.select_images) { - this.trigger("saved", this.active.save()); - this.close(); + this.final_data = this.active.save(); + this._super.apply(this, arguments); return; } if(this.rte) { @@ -198,8 +198,8 @@ var MediaDialog = Dialog.extend({ } var media = this.active.media; - $(document.body).trigger("media-saved", [media, self.old_media]); - self.trigger("saved", [media, self.old_media]); + this.final_data = [media, self.old_media]; + $(document.body).trigger("media-saved", this.final_data); // Update editor bar after image edition (in case the image change to icon or other) _.defer(function () { @@ -312,10 +312,8 @@ var ImageDialog = Widget.extend({ }, save: function () { if (this.options.select_images) { - this.parent.trigger("save", this.images); return this.images; } - this.parent.trigger("save", this.media); var img = this.images[0]; if (!img) { @@ -668,7 +666,6 @@ var fontIconsDialog = Widget.extend({ */ save: function () { var self = this; - this.parent.trigger("save", this.media); var style = this.media.attributes.style ? this.media.attributes.style.value : ''; var classes = (this.media.className||"").split(/\s+/); var custom_classes = /^fa(-[1-5]x|spin|rotate-(9|18|27)0|flip-(horizont|vertic)al|fw|border)?$/; @@ -684,6 +681,8 @@ var fontIconsDialog = Widget.extend({ style = style.replace(/\s*width:[^;]+/, ''); } $(this.media).attr("class", _.compact(final_classes).join(' ')).attr("style", style); + + return this.media; }, /** * return the data font object (with base, parser and icons) or null @@ -907,7 +906,6 @@ var VideoDialog = Widget.extend({ return false; }, save: function () { - this.parent.trigger("save", this.media); var video_id = this.$("#video_id").val(); if (!video_id) { this.$("button.btn-primary").click(); @@ -921,6 +919,8 @@ var VideoDialog = Widget.extend({ '
'); $(this.media).replaceWith($iframe); this.media = $iframe[0]; + + return this.media; }, clear: function () { if (this.media.dataset.src) { @@ -1095,7 +1095,7 @@ var LinkDialog = Dialog.extend({ if (classes.replace(/(^|[ ])(btn-default|btn-success|btn-primary|btn-info|btn-warning|btn-danger)([ ]|$)/gi, ' ')) { self.data.style = {'background-color': '', 'color': ''}; } - self.trigger("save", self.data); + self.final_data = self.data; }).then(_super); }, bind_data: function () { diff --git a/addons/website/static/src/js/website.contentMenu.js b/addons/website/static/src/js/website.contentMenu.js index 82bbefcf8c2..aa695857746 100644 --- a/addons/website/static/src/js/website.contentMenu.js +++ b/addons/website/static/src/js/website.contentMenu.js @@ -186,8 +186,8 @@ var SelectEditMenuDialog = widget.Dialog.extend({ }, options || {})); }, save: function () { - this.trigger("save", parseInt(this.$el.find("select").val() || null)); - this._super(); + this.final_data = parseInt(this.$el.find("select").val() || null); + this._super.apply(this, arguments); } }); diff --git a/addons/website/static/src/js/website.js b/addons/website/static/src/js/website.js index 046da64ccf7..c765e335f7c 100644 --- a/addons/website/static/src/js/website.js +++ b/addons/website/static/src/js/website.js @@ -175,9 +175,10 @@ odoo.define('website.website', function (require) { }); /* ----- PUBLISHING STUFF ---- */ - $(document).on('click', '.js_publish_management .js_publish_btn', function () { + $(document).on('click', '.js_publish_management .js_publish_btn', function (e) { + e.preventDefault(); + var $data = $(this).parents(".js_publish_management:first"); - var self=this; ajax.jsonRpc($data.data('controller') || '/website/publish', 'call', {'id': +$data.data('id'), 'object': $data.data('object')}) .then(function (result) { $data.toggleClass("css_unpublished css_published"); diff --git a/addons/website/static/src/js/website.seo.js b/addons/website/static/src/js/website.seo.js index 4b5acc8090a..09e6c00a701 100644 --- a/addons/website/static/src/js/website.seo.js +++ b/addons/website/static/src/js/website.seo.js @@ -343,8 +343,8 @@ var Configurator = Dialog.extend({ template: 'website.seo_configuration', events: { 'keyup input[name=seo_page_keywords]': 'confirmKeyword', - 'keyup input[name=seo_page_title]': 'titleChanged', - 'keyup textarea[name=seo_page_description]': 'descriptionChanged', + 'blur input[name=seo_page_title]': 'titleChanged', + 'blur textarea[name=seo_page_description]': 'descriptionChanged', 'click button[data-action=add]': 'addKeyword', }, canEditTitle: false, diff --git a/addons/website/static/src/js/website.snippets.gallery.js b/addons/website/static/src/js/website.snippets.gallery.js index 481ba06e6f3..cde906ca764 100644 --- a/addons/website/static/src/js/website.snippets.gallery.js +++ b/addons/website/static/src/js/website.snippets.gallery.js @@ -67,7 +67,7 @@ options.registry.gallery = options.Class.extend({ }, bind_change: function () { var self = this; - return this.$target.find("img").off('saved').on('saved', function (event, img) { + return this.$target.find("img").off('save').on('save', function (event, img) { var $parent = $(img).parent(); $parent.addClass("saved_active"); var index = self.$target.find(".item.saved_active").index(); @@ -228,7 +228,7 @@ options.registry.gallery = options.Class.extend({ var $container = this.$target.find(".container:first"); var editor = new widget.MediaDialog(null, {select_images: true}, this.$target.closest('.o_editable'), null).open(); var index = Math.max(0, _.max(_.map(this.$target.find("img").get(), function (img) { return img.dataset.index | 0; })) + 1); - editor.on('saved', this, function (attachments) { + editor.on('save', this, function (attachments) { for (var i = 0 ; i < attachments.length; i++) { $('') .attr("src", attachments[i].src) diff --git a/addons/website_blog/static/src/js/website_blog.editor.js b/addons/website_blog/static/src/js/website_blog.editor.js index c49ed85548b..85a2d2d7901 100644 --- a/addons/website_blog/static/src/js/website_blog.editor.js +++ b/addons/website_blog/static/src/js/website_blog.editor.js @@ -110,7 +110,7 @@ odoo.define('website_blog.editor', function (require) { var $image = $("", {src: this.$image.css("background-image")}); var editor = new widget.MediaDialog(null, {only_images: true}, $image, $image[0]).open(); - editor.on("saved", this, function (event, img) { + editor.on("save", this, function (event, img) { var src = $image.attr("src"); this.$image.css("background-image", src ? ("url(" + src + ")") : ""); if (!this.$target.hasClass("cover")) { diff --git a/addons/website_blog/views/website_blog_templates.xml b/addons/website_blog/views/website_blog_templates.xml index 93c97a9c8ac..f6a4ac3a185 100644 --- a/addons/website_blog/views/website_blog_templates.xml +++ b/addons/website_blog/views/website_blog_templates.xml @@ -124,12 +124,12 @@
-

-

No blog post yet.

-

+

+

No blog post yet.

+

- Click on "Content" on the top menu to write your first blog post. -

+ Click on "New Page" in the top-right corner to write your first blog post. +

diff --git a/addons/website_sale/models/sale_order.py b/addons/website_sale/models/sale_order.py index 9052e9a5dc3..a8a25ee3bdb 100644 --- a/addons/website_sale/models/sale_order.py +++ b/addons/website_sale/models/sale_order.py @@ -295,6 +295,11 @@ class Website(models.Model): def sale_product_domain(self): return [("sale_ok", "=", True)] + @api.model + def sale_get_payment_term(self, partner): + DEFAULT_PAYMENT_TERM = 'account.account_payment_term_immediate' + return self.env.ref(DEFAULT_PAYMENT_TERM, False).id or partner.property_payment_term_id.id + @api.multi def sale_get_order(self, force_create=False, code=None, update_pricelist=False, force_pricelist=False): """ Return the current sale order after mofications specified by params. @@ -339,7 +344,7 @@ class Website(models.Model): sale_order = self.env['sale.order'].sudo().create({ 'partner_id': partner.id, 'pricelist_id': pricelist_id, - 'payment_term_id': partner.property_payment_term_id.id, + 'payment_term_id': self.sale_get_payment_term(partner), 'team_id': self.salesteam_id.id, 'partner_invoice_id': addr['invoice'], 'partner_shipping_id': addr['delivery'], @@ -380,6 +385,7 @@ class Website(models.Model): sale_order.write({'partner_id': partner.id}) sale_order.onchange_partner_id() sale_order.onchange_partner_shipping_id() # fiscal position + sale_order['payment_term_id'] = self.sale_get_payment_term(partner) # check the pricelist : update it if the pricelist is not the 'forced' one values = {} diff --git a/addons/website_sale/static/src/css/website_sale.css b/addons/website_sale/static/src/css/website_sale.css index d5c3d999e47..f1def903329 100644 --- a/addons/website_sale/static/src/css/website_sale.css +++ b/addons/website_sale/static/src/css/website_sale.css @@ -366,6 +366,7 @@ div#payment_method .list-group-item { min-height: 100px; position: relative; padding-bottom: 5px; + padding-left: 180px; } .oe_product.oe_list .oe_product_image { top: 0; @@ -377,9 +378,10 @@ div#payment_method .list-group-item { .oe_product.oe_list section { position: relative; border: 0; - top: 0; + top: auto; bottom: auto; - left: 180px; + left: auto; + right: auto; background: transparent; } } diff --git a/addons/website_sale/static/src/css/website_sale.sass b/addons/website_sale/static/src/css/website_sale.sass index e6dc2a037ad..7cc6cfc0341 100644 --- a/addons/website_sale/static/src/css/website_sale.sass +++ b/addons/website_sale/static/src/css/website_sale.sass @@ -314,6 +314,7 @@ div#payment_method min-height: 100px position: relative padding-bottom: 5px + padding-left: 180px .oe_product_image top: 0 bottom: 0 @@ -323,9 +324,10 @@ div#payment_method section position: relative border: 0 - top: 0 + top: auto bottom: auto - left: 180px + left: auto + right: auto background: transparent .oe_website_sale .row .row .col-md-12 diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml index ce62fbf6af4..be19877fb82 100644 --- a/addons/website_sale/views/templates.xml +++ b/addons/website_sale/views/templates.xml @@ -1139,7 +1139,8 @@ diff --git a/addons/website_slides/static/src/img/channel-default.jpg b/addons/website_slides/static/src/img/channel-default.jpg new file mode 100644 index 00000000000..dce2a23b9b2 Binary files /dev/null and b/addons/website_slides/static/src/img/channel-default.jpg differ diff --git a/addons/website_slides/view/website_slides.xml b/addons/website_slides/view/website_slides.xml index d5d0826889c..0441b030ed9 100644 --- a/addons/website_slides/view/website_slides.xml +++ b/addons/website_slides/view/website_slides.xml @@ -44,7 +44,7 @@
diff --git a/doc/cla/individual/bfotop.md b/doc/cla/individual/bfotop.md new file mode 100644 index 00000000000..8697a595ec7 --- /dev/null +++ b/doc/cla/individual/bfotop.md @@ -0,0 +1,11 @@ + + +Greece, 2016-09-23 + +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, + +Basilis Fotopoulos bfotop@tax-solutions.gr https://github.com/bfotop diff --git a/doc/cla/individual/kaerdsar.md b/doc/cla/individual/kaerdsar.md new file mode 100644 index 00000000000..70e63803522 --- /dev/null +++ b/doc/cla/individual/kaerdsar.md @@ -0,0 +1,11 @@ +Cuba, 08/10/2016 + +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, + +Cesar Lage kaerdsar@gmail.com https://github.com/kaerdsar diff --git a/doc/cla/individual/mcb30.md b/doc/cla/individual/mcb30.md new file mode 100644 index 00000000000..b3c601a1ff6 --- /dev/null +++ b/doc/cla/individual/mcb30.md @@ -0,0 +1,11 @@ +United Kingdom, 2016-08-09 + +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, + +Michael Brown mbrown@fensystems.co.uk https://github.com/mcb30 diff --git a/doc/setup/install.rst b/doc/setup/install.rst index 0f1de4f14c0..3e5940d8ba1 100644 --- a/doc/setup/install.rst +++ b/doc/setup/install.rst @@ -61,11 +61,11 @@ refer to :ref:`setup/enterprise` (except for :ref:`setup/install/source`). If you wish to access the Enterprise installers/source code, you can: * Go to the Download_ page and log in with your customer credentials -* Download the source on GitHub using git_ +* Download the source on GitHub using git_(available to partners only) .. note:: If you do not have access to our Enterprise repository, you can request it be e-mailing your sales representative or our online support with - your subscription number and GitHub username. + your subscription number and GitHub username. (available to partners only) .. warning:: Enterprise deb and rpm packages do not have repositories, so automatic update will not work. Reinstalling the latest package version will be needed diff --git a/odoo/addons/base/ir/ir_config_parameter.py b/odoo/addons/base/ir/ir_config_parameter.py index 5634277c06b..744ac82db50 100644 --- a/odoo/addons/base/ir/ir_config_parameter.py +++ b/odoo/addons/base/ir/ir_config_parameter.py @@ -5,9 +5,12 @@ Store database-specific configuration parameters """ import uuid +import logging from odoo import api, fields, models -from odoo.tools import config, ormcache +from odoo.tools import config, ormcache, mute_logger + +_logger = logging.getLogger(__name__) """ A dictionary holding some configuration parameters to be initialized when the database is created. @@ -34,6 +37,7 @@ class IrConfigParameter(models.Model): ] @api.model_cr + @mute_logger('openerp.addons.base.ir.ir_config_parameter') def init(self, force=False): """ Initializes the parameters listed in _default_parameters. @@ -82,6 +86,8 @@ class IrConfigParameter(models.Model): group = self.env.ref(group_xml, raise_if_not_found=False) if group: gids.append((4, group.id)) + else: + _logger.warning('Potential Security Issue: Group [%s] is not found.' % group_xml) vals = {'value': value} if gids: diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py index 845b23c44e1..ebc3dd616f7 100644 --- a/odoo/tools/translate.py +++ b/odoo/tools/translate.py @@ -305,12 +305,16 @@ def html_translate(callback, value): if not value: return value - parser = etree.HTMLParser(encoding='utf-8') - trans = XMLTranslator(callback, 'html', parser) - wrapped = "
%s
" % encode(value) - root = etree.fromstring(wrapped, parser) - trans.process(root[0][0]) # html > body > div - return trans.get_done()[5:-6] # remove tags
and
+ try: + parser = etree.HTMLParser(encoding='utf-8') + trans = XMLTranslator(callback, 'html', parser) + wrapped = "
%s
" % encode(value) + root = etree.fromstring(wrapped, parser) + trans.process(root[0][0]) # html > body > div + value = trans.get_done()[5:-6] # remove tags
and
+ except ValueError: + _logger.exception("Cannot translate malformed HTML, using source value instead") + return value #