diff --git a/addons/account/models/chart_template.py b/addons/account/models/chart_template.py index d3d6162a247..1284ce967fc 100644 --- a/addons/account/models/chart_template.py +++ b/addons/account/models/chart_template.py @@ -283,7 +283,8 @@ class AccountChartTemplate(models.Model): tmp1, tmp2 = self.parent_id._install_template(company, code_digits=code_digits, transfer_account_id=transfer_account_id, acc_ref=acc_ref, taxes_ref=taxes_ref) acc_ref.update(tmp1) taxes_ref.update(tmp2) - tmp1, tmp2 = self._load_template(company, code_digits=code_digits, transfer_account_id=transfer_account_id, account_ref=acc_ref, taxes_ref=taxes_ref) + # Ensure, even if individually, that everything is translated according to the company's language. + tmp1, tmp2 = self.with_context(lang=company.partner_id.lang)._load_template(company, code_digits=code_digits, transfer_account_id=transfer_account_id, account_ref=acc_ref, taxes_ref=taxes_ref) acc_ref.update(tmp1) taxes_ref.update(tmp2) return acc_ref, taxes_ref @@ -805,6 +806,8 @@ class WizardMultiChartsAccounts(models.TransientModel): all the provided information to create the accounts, the banks, the journals, the taxes, the accounting properties... accordingly for the chosen company. ''' + # Ensure everything is translated consitingly to the company's language, not the user's one. + self = self.with_context(lang=self.company_id.partner_id.lang) if len(self.env['account.account'].search([('company_id', '=', self.company_id.id)])) > 0: # We are in a case where we already have some accounts existing, meaning that user has probably # created its own accounts and does not need a coa, so skip installation of coa. diff --git a/addons/hr_holidays/report/holidays_summary_report.py b/addons/hr_holidays/report/holidays_summary_report.py index 0118429725f..67f4c96a861 100644 --- a/addons/hr_holidays/report/holidays_summary_report.py +++ b/addons/hr_holidays/report/holidays_summary_report.py @@ -68,8 +68,8 @@ class HrHolidaySummaryReport(models.AbstractModel): for index in range(0, ((date_to - date_from).days + 1)): if date_from >= start_date and date_from <= end_date: res[(date_from-start_date).days]['color'] = holiday.holiday_status_id.color_name - count+=1 date_from += timedelta(1) + count += abs(holiday.number_of_days) self.sum = count return res diff --git a/addons/l10n_be_intrastat/wizard/xml_decl.py b/addons/l10n_be_intrastat/wizard/xml_decl.py index db814691b77..80d7d617ef4 100644 --- a/addons/l10n_be_intrastat/wizard/xml_decl.py +++ b/addons/l10n_be_intrastat/wizard/xml_decl.py @@ -164,6 +164,7 @@ class XmlDeclaration(models.TransientModel): lines = self.env.cr.fetchall() invoicelines_ids = [rec[0] for rec in lines] invoicelines = self.env['account.invoice.line'].browse(invoicelines_ids) + for inv_line in invoicelines: #Check type of transaction @@ -189,6 +190,8 @@ class XmlDeclaration(models.TransientModel): #comes from purchase po_lines = self.env['purchase.order.line'].search([('invoice_lines', 'in', inv_line.id)], limit=1) if po_lines: + if self._is_situation_triangular(company, po_line=po_lines): + continue location = self.env['stock.location'].browse(po_lines.order_id._get_destination_location()) region_id = self.env['stock.warehouse'].get_regionid_from_locationid(location) if region_id: @@ -197,6 +200,8 @@ class XmlDeclaration(models.TransientModel): #comes from sales so_lines = self.env['sale.order.line'].search([('invoice_lines', 'in', inv_line.id)], limit=1) if so_lines: + if self._is_situation_triangular(company, so_line=so_lines): + continue saleorder = so_lines.order_id if saleorder and saleorder.warehouse_id and saleorder.warehouse_id.region_id: exreg = IntrastatRegion.browse(saleorder.warehouse_id.region_id.id).code @@ -286,3 +291,24 @@ class XmlDeclaration(models.TransientModel): dim = ET.SubElement(item, 'Dim') dim.set('prop', prop) dim.text = value + + def _is_situation_triangular(self, company, po_line=False, so_line=False): + # Ignoring what is purchased and sold by us with a dropshipping route + # outside of our country, or completely within it + # https://www.nbb.be/doc/dq/f_pdf_ex/intra2017fr.pdf (ยง 4.x) + dropship_pick_type = self.env.ref('stock_dropshipping.picking_type_dropship', raise_if_not_found=False) + if not dropship_pick_type: + return False + stock_move_domain = [('picking_type_id', '=', dropship_pick_type.id)] + + if po_line: + stock_move_domain.append(('purchase_line_id', '=', po_line.id)) + if so_line: + stock_move_domain.append(('procurement_id.sale_line_id', '=', so_line.id)) + + stock_move = self.env['stock.move'].search(stock_move_domain, limit=1) + return stock_move and ( + (stock_move.partner_id.country_id.code != company.country_id.code and + stock_move.picking_partner_id.country_id.code != company.country_id.code) or + (stock_move.partner_id.country_id.code == company.country_id.code and + stock_move.picking_partner_id.country_id.code == company.country_id.code)) diff --git a/addons/mail/static/src/js/client_action.js b/addons/mail/static/src/js/client_action.js index fa77674a6b9..2ef2c5075ac 100644 --- a/addons/mail/static/src/js/client_action.js +++ b/addons/mail/static/src/js/client_action.js @@ -244,6 +244,9 @@ var ChatAction = Widget.extend(ControlPanelMixin, { var def3 = this.extended_composer.appendTo(this.$('.o_mail_chat_content')); var def4 = this.searchview.appendTo($("
")).then(function () { self.$searchview_buttons = self.searchview.$buttons.contents(); + // manually call do_search to generate the initial domain and filter + // the messages in the default channel + self.searchview.do_search(); }); this.render_sidebar(); @@ -556,7 +559,12 @@ var ChatAction = Widget.extend(ControlPanelMixin, { }); this.domain = result.domain; - this.fetch_and_render_thread(); + if (this.channel) { + // initially (when do_search is called manually), there is no + // channel set yet, so don't try to fetch and render the thread as + // this will be done as soon as the default channel is set + this.fetch_and_render_thread(); + } }, on_post_message: function (message) { diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py index 56ae367c27b..2ffae162cf4 100644 --- a/addons/mrp/models/stock_move.py +++ b/addons/mrp/models/stock_move.py @@ -282,7 +282,7 @@ class StockMove(models.Model): preferred_domain_list = [preferred_domain] + [fallback_domain] + [fallback_domain2] if move.has_tracking == 'none': quants = quant_obj.quants_get_preferred_domain(move.product_qty, move, domain=main_domain, preferred_domain_list=preferred_domain_list) - self.env['stock.quant'].quants_move(quants, move, move.location_dest_id) + self.env['stock.quant'].quants_move(quants, move, move.location_dest_id, owner_id=move.restrict_partner_id.id) else: for movelot in move.active_move_lot_ids: if float_compare(movelot.quantity_done, 0, precision_rounding=rounding) > 0: @@ -290,7 +290,7 @@ class StockMove(models.Model): raise UserError(_('You need to supply a lot/serial number.')) qty = move.product_uom._compute_quantity(movelot.quantity_done, move.product_id.uom_id) quants = quant_obj.quants_get_preferred_domain(qty, move, lot_id=movelot.lot_id.id, domain=main_domain, preferred_domain_list=preferred_domain_list) - self.env['stock.quant'].quants_move(quants, move, move.location_dest_id, lot_id = movelot.lot_id.id) + self.env['stock.quant'].quants_move(quants, move, move.location_dest_id, lot_id = movelot.lot_id.id, owner_id=move.restrict_partner_id.id) moves_to_unreserve |= move # Next move in production order if move.move_dest_id and move.move_dest_id.state not in ('done', 'cancel'): diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py index 8fae67a2378..a54e60acade 100644 --- a/addons/purchase/models/purchase.py +++ b/addons/purchase/models/purchase.py @@ -1167,11 +1167,15 @@ class ProductCategory(models.Model): class MailComposeMessage(models.TransientModel): _inherit = 'mail.compose.message' + @api.multi + def mail_purchase_order_on_send(self): + if not self.filtered('subtype_id.internal'): + order = self.env['purchase.order'].browse(self._context['default_res_id']) + if order.state == 'draft': + order.state = 'sent' + @api.multi def send_mail(self, auto_commit=False): if self._context.get('default_model') == 'purchase.order' and self._context.get('default_res_id'): - if not self.filtered('subtype_id.internal'): - order = self.env['purchase.order'].browse([self._context['default_res_id']]) - if order.state == 'draft': - order.state = 'sent' + self.mail_purchase_order_on_send() return super(MailComposeMessage, self.with_context(mail_post_autofollow=True)).send_mail(auto_commit=auto_commit) diff --git a/addons/purchase/views/account_invoice_views.xml b/addons/purchase/views/account_invoice_views.xml index 7f4fc3bf4e0..3f23f6777d0 100644 --- a/addons/purchase/views/account_invoice_views.xml +++ b/addons/purchase/views/account_invoice_views.xml @@ -59,7 +59,7 @@ - diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index f25c82f7cb2..a91ed8862fb 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -737,6 +737,18 @@ class SaleOrderLine(models.Model): msg += _("Invoiced Quantity") + ": %s
" % (line.qty_invoiced,) msg += "" order.message_post(body=msg) + + # Prevent writing on a locked SO. + protected_fields = self._get_protected_fields() + if 'done' in self.mapped('order_id.state') and any(f in values.keys() for f in protected_fields): + fields = self.env['ir.model.fields'].search([ + ('name', 'in', protected_fields), ('model', '=', self._name) + ]) + raise UserError( + _('It is forbidden to modify the following fields in a locked order:\n%s') + % '\n'.join(fields.mapped('field_description')) + ) + result = super(SaleOrderLine, self).write(values) if lines: lines._action_procurement_create() @@ -995,6 +1007,12 @@ class SaleOrderLine(models.Model): return product[field_name] * uom_factor * cur_factor, currency_id.id + def _get_protected_fields(self): + return [ + 'product_id', 'name', 'price_unit', 'product_uom', 'product_uom_qty', + 'tax_id', 'analytic_tag_ids' + ] + @api.onchange('product_id', 'price_unit', 'product_uom', 'product_uom_qty', 'tax_id') def _onchange_discount(self): self.discount = 0.0 diff --git a/addons/sale/wizard/sale_make_invoice_advance.py b/addons/sale/wizard/sale_make_invoice_advance.py index 2b9caf659bb..ac7d9587d22 100644 --- a/addons/sale/wizard/sale_make_invoice_advance.py +++ b/addons/sale/wizard/sale_make_invoice_advance.py @@ -76,12 +76,14 @@ class SaleAdvancePaymentInv(models.TransientModel): if self.amount <= 0.00: raise UserError(_('The value of the down payment amount must be positive.')) + context = {'lang': order.partner_id.lang} if self.advance_payment_method == 'percentage': amount = order.amount_untaxed * self.amount / 100 name = _("Down payment of %s%%") % (self.amount,) else: amount = self.amount name = _('Down Payment') + del context taxes = self.product_id.taxes_id.filtered(lambda r: not order.company_id or r.company_id == order.company_id) if order.fiscal_position_id and taxes: tax_ids = order.fiscal_position_id.map_tax(taxes).ids @@ -152,6 +154,7 @@ class SaleAdvancePaymentInv(models.TransientModel): tax_ids = order.fiscal_position_id.map_tax(taxes).ids else: tax_ids = taxes.ids + context = {'lang': order.partner_id.lang} so_line = sale_line_obj.create({ 'name': _('Advance: %s') % (time.strftime('%m %Y'),), 'price_unit': amount, @@ -163,6 +166,7 @@ class SaleAdvancePaymentInv(models.TransientModel): 'tax_id': [(6, 0, tax_ids)], 'is_downpayment': True, }) + del context self._create_invoice(order, so_line, amount) if self._context.get('open_invoices', False): return sale_orders.action_view_invoice() diff --git a/addons/stock/i18n/stock.pot b/addons/stock/i18n/stock.pot index 95f509d1bdb..5bf3ed17598 100644 --- a/addons/stock/i18n/stock.pot +++ b/addons/stock/i18n/stock.pot @@ -15,6 +15,22 @@ msgstr "" "Content-Transfer-Encoding: \n" "Plural-Forms: \n" +#. module: stock +#: code:addons/stock/models/stock_move.py:230 +#, python-format +msgid "\n" +"\n" +"%s --> Product UoM is %s (%s) - Move UoM is %s (%s)" +msgstr "" + +#. module: stock +#: code:addons/stock/models/stock_move.py:231 +#, python-format +msgid "\n" +"\n" +"Blocking: %s" +msgstr "" + #. module: stock #: code:addons/stock/models/stock_move.py:214 #, python-format diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index a6ec6b86359..a5b031f8205 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -223,10 +223,12 @@ class StockMove(models.Model): @api.constrains('product_uom') def _check_uom(self): - moves_error = self.filtered(lambda move: move.product_id.uom_id.category_id.id != move.product_uom.category_id.id) + moves_error = self.filtered(lambda move: move.product_id.uom_id.category_id != move.product_uom.category_id) if moves_error: user_warning = _('You try to move a product using a UoM that is not compatible with the UoM of the product moved. Please use an UoM in the same UoM category.') - user_warning += '\n\nBlocking: %s' % ' ,'.join(moves_error.mapped('name')) + for move in moves_error: + user_warning += _('\n\n%s --> Product UoM is %s (%s) - Move UoM is %s (%s)') % (move.product_id.display_name, move.product_id.uom_id.name, move.product_id.uom_id.category_id.name, move.product_uom.name, move.product_uom.category_id.name) + user_warning += _('\n\nBlocking: %s') % ' ,'.join(moves_error.mapped('name')) raise UserError(user_warning) @api.model_cr diff --git a/addons/stock/models/stock_warehouse.py b/addons/stock/models/stock_warehouse.py index 69b19762ffd..ac180bdd7cb 100644 --- a/addons/stock/models/stock_warehouse.py +++ b/addons/stock/models/stock_warehouse.py @@ -457,6 +457,7 @@ class Warehouse(models.Model): self.Routing(warehouse.lot_stock_id, warehouse.wh_pack_stock_loc_id, warehouse.pick_type_id), self.Routing(warehouse.wh_pack_stock_loc_id, warehouse.wh_output_stock_loc_id, warehouse.pack_type_id), self.Routing(warehouse.wh_output_stock_loc_id, customer_loc, warehouse.out_type_id)], + 'company_id': warehouse.company_id.id, }) for warehouse in self) @api.multi @@ -466,6 +467,7 @@ class Warehouse(models.Model): 'product_categ_selectable': True, 'product_selectable': False, 'sequence': 10, + 'company_id': self.company_id.id, } @api.model @@ -485,7 +487,8 @@ class Warehouse(models.Model): 'product_selectable': True, 'product_categ_selectable': True, 'supplied_wh_id': self.id, - 'supplier_wh_id': supplier_warehouse.id} + 'supplier_wh_id': supplier_warehouse.id, + 'company_id': self.company_id.id} def _get_crossdock_route_values(self): return { @@ -494,7 +497,8 @@ class Warehouse(models.Model): 'product_selectable': True, 'product_categ_selectable': True, 'active': self.delivery_steps != 'ship_only' and self.reception_steps != 'one_step', - 'sequence': 20} + 'sequence': 20, + 'company_id': self.company_id.id} # Pull / Push tools # ------------------------------------------------------------ diff --git a/addons/stock/views/stock_quant_views.xml b/addons/stock/views/stock_quant_views.xml index ad1f07d22b9..47bf98b9cc4 100644 --- a/addons/stock/views/stock_quant_views.xml +++ b/addons/stock/views/stock_quant_views.xml @@ -116,6 +116,7 @@ +
diff --git a/addons/stock_account/models/product.py b/addons/stock_account/models/product.py index 1e4ac7ffbe1..751d182d30d 100644 --- a/addons/stock_account/models/product.py +++ b/addons/stock_account/models/product.py @@ -97,7 +97,9 @@ class ProductProduct(models.Model): """ Changes the Standard Price of Product and creates an account move accordingly.""" AccountMove = self.env['account.move'] - locations = self.env['stock.location'].search([('usage', '=', 'internal'), ('company_id', '=', self.env.user.company_id.id)]) + quant_locs = self.env['stock.quant'].sudo().read_group([('product_id', 'in', self.ids)], ['location_id'], ['location_id']) + quant_loc_ids = [loc['location_id'][0] for loc in quant_locs] + locations = self.env['stock.location'].search([('usage', '=', 'internal'), ('company_id', '=', self.env.user.company_id.id), ('id', 'in', quant_loc_ids)]) product_accounts = {product.id: product.product_tmpl_id.get_product_accounts() for product in self} diff --git a/addons/website_forum/models/forum.py b/addons/website_forum/models/forum.py index f77d8dca4fd..7864b95b72f 100644 --- a/addons/website_forum/models/forum.py +++ b/addons/website_forum/models/forum.py @@ -442,7 +442,7 @@ class Post(models.Model): elif post.parent_id and not post.can_answer: raise KarmaError('Not enough karma to answer to a question') if not post.parent_id and not post.can_post: - post.state = 'pending' + post.sudo().state = 'pending' # add karma for posting new questions if not post.parent_id and post.state == 'active': @@ -471,11 +471,18 @@ class Post(models.Model): @api.multi def write(self, vals): + trusted_keys = ['active', 'is_correct', 'tag_ids'] # fields where security is checked manually if 'content' in vals: vals['content'] = self._update_content(vals['content'], self.forum_id.id) if 'state' in vals: - if vals['state'] in ['active', 'close'] and any(not post.can_close for post in self): - raise KarmaError('Not enough karma to close or reopen a post.') + if vals['state'] in ['active', 'close']: + if any(not post.can_close for post in self): + raise KarmaError('Not enough karma to close or reopen a post.') + trusted_keys += ['state', 'closed_uid', 'closed_date', 'closed_reason_id'] + elif vals['state'] == 'flagged': + if any(not post.can_flag for post in self): + raise KarmaError('Not enough karma to flag a post.') + trusted_keys += ['state', 'flag_user_id'] if 'active' in vals: if any(not post.can_unlink for post in self): raise KarmaError('Not enough karma to delete or reactivate a post') @@ -492,7 +499,7 @@ class Post(models.Model): tag_ids = set(tag.get('id') for tag in self.resolve_2many_commands('tag_ids', vals['tag_ids'])) if any(set(post.tag_ids) != tag_ids for post in self) and any(self.env.user.karma < post.forum_id.karma_edit_retag for post in self): raise KarmaError(_('Not enough karma to retag.')) - if any(key not in ['state', 'active', 'is_correct', 'closed_uid', 'closed_date', 'closed_reason_id', 'tag_ids'] for key in vals.keys()) and any(not post.can_edit for post in self): + if any(key not in trusted_keys for key in vals) and any(not post.can_edit for post in self): raise KarmaError('Not enough karma to edit a post.') res = super(Post, self).write(vals) diff --git a/addons/website_forum/views/ir_qweb.xml b/addons/website_forum/views/ir_qweb.xml index 671ae21944d..f823262d91b 100644 --- a/addons/website_forum/views/ir_qweb.xml +++ b/addons/website_forum/views/ir_qweb.xml @@ -5,9 +5,9 @@
diff --git a/addons/website_portal_sale/models/payment.py b/addons/website_portal_sale/models/payment.py index 176f041847a..ace38093a69 100644 --- a/addons/website_portal_sale/models/payment.py +++ b/addons/website_portal_sale/models/payment.py @@ -12,6 +12,7 @@ class PaymentTransaction(models.Model): # link with the sales order sale_order_id = fields.Many2one('sale.order', string='Sales Order', auto_join=True) + so_state = fields.Selection('sale.order', string='Sale Order State', related='sale_order_id.state') def _generate_and_pay_invoice(self, tx, acquirer_name): tx.sale_order_id._force_lines_to_invoice_policy_order() diff --git a/addons/website_quote/models/sale_order.py b/addons/website_quote/models/sale_order.py index b3e17b42827..d09ffc53bcc 100644 --- a/addons/website_quote/models/sale_order.py +++ b/addons/website_quote/models/sale_order.py @@ -263,7 +263,8 @@ class SaleOrderOption(models.Model): order_line = order.order_line.filtered(lambda line: line.product_id == self.product_id) if order_line: - order_line[0].product_uom_qty += 1 + order_line = order_line[0] + order_line.product_uom_qty += 1 else: vals = { 'price_unit': self.price_unit,