diff --git a/addons/event/models/event.py b/addons/event/models/event.py index d040ca7ba1a..f0d6d0312cf 100644 --- a/addons/event/models/event.py +++ b/addons/event/models/event.py @@ -138,7 +138,7 @@ class EventEvent(models.Model): 'event.registration', 'event_id', string='Attendees', readonly=False, states={'done': [('readonly', True)]}) # Date fields - date_tz = fields.Selection('_tz_get', string='Timezone', required=True, default=lambda self: self.env.user.tz) + date_tz = fields.Selection('_tz_get', string='Timezone', required=True, default=lambda self: self.env.user.tz or 'UTC') date_begin = fields.Datetime( string='Start Date', required=True, track_visibility='onchange', states={'done': [('readonly', True)]}) diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index ebe11eb09d9..c672cab2835 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -2081,6 +2081,7 @@ class MailThread(models.AbstractModel): partner_ids=[(4, pid) for pid in partner_ids], auto_delete=True, auto_delete_message=True, + parent_id=False, # override accidental context defaults subtype_id=self.env.ref('mail.mt_note').id) @api.multi diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py index 5f94e7280fa..e307cd02c24 100644 --- a/addons/point_of_sale/models/pos_order.py +++ b/addons/point_of_sale/models/pos_order.py @@ -238,7 +238,7 @@ class PosOrder(models.Model): have_to_group_by = session and session.config_id.group_by or False rounding_method = session and session.config_id.company_id.tax_calculation_rounding_method - for order in self.filtered(lambda o: not o.account_move or order.state == 'paid'): + for order in self.filtered(lambda o: not o.account_move or o.state == 'paid'): current_company = order.sale_journal.company_id account_def = IrProperty.get( 'property_account_receivable_id', 'res.partner') diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 6d1779ce398..dfd4b5b9b29 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -1213,6 +1213,7 @@ exports.Orderline = Backbone.Model.extend({ orderline.quantity = this.quantity; orderline.quantityStr = this.quantityStr; orderline.discount = this.discount; + orderline.price = this.price; orderline.type = this.type; orderline.selected = false; return orderline; diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 8e24db3c167..c162d4fbc15 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -1421,8 +1421,12 @@


- Phone:
- User:
+
+ Phone:
+
+
+ User:
+

diff --git a/addons/product/i18n/product.pot b/addons/product/i18n/product.pot index 72db98d6569..8e9efa08c48 100644 --- a/addons/product/i18n/product.pot +++ b/addons/product/i18n/product.pot @@ -1854,6 +1854,18 @@ msgstr "" msgid "You cannot define the decimal precision of 'Account' as greater than the rounding factor of the company's main currency" msgstr "" +#. module: product +#: code:addons/product/wizard/product_price_list.py:26 +#, python-format +msgid "You have to set a logo for your company." +msgstr "" + +#. module: product +#: code:addons/product/wizard/product_price_list.py:28 +#, python-format +msgid "You have to set your reports's header and footer layout." +msgstr "" + #. module: product #: model:ir.actions.act_window,help:product.product_uom_form_action msgid "You must define a conversion rate between several Units of\n" diff --git a/addons/product/models/product_template.py b/addons/product/models/product_template.py index 1564cf80c07..efb701a6092 100644 --- a/addons/product/models/product_template.py +++ b/addons/product/models/product_template.py @@ -7,7 +7,7 @@ import psycopg2 from odoo.addons import decimal_precision as dp from odoo import api, fields, models, tools, _ -from odoo.exceptions import ValidationError, except_orm +from odoo.exceptions import ValidationError, RedirectWarning, except_orm from odoo.tools import pycompat @@ -21,7 +21,16 @@ class ProductTemplate(models.Model): if self._context.get('categ_id') or self._context.get('default_categ_id'): return self._context.get('categ_id') or self._context.get('default_categ_id') category = self.env.ref('product.product_category_all', raise_if_not_found=False) - return category.id if category.type == 'normal' else False + if category and category.type == 'normal': + return category.id + # fallback if no category "All" exists, because yes, users do delete default data! + category = self.env['product.category'].search([('type', '=', 'normal')], limit=1) + if category: + return category.id + else: + err_msg = _('You must define at least one product category (that is not a view) in order to be able to create products.') + redir_msg = _('Go to Internal Categories') + raise RedirectWarning(err_msg, self.env.ref('product.product_category_action_form').id, redir_msg) def _get_default_uom_id(self): return self.env["product.uom"].search([], limit=1, order='id').id diff --git a/addons/product/wizard/product_price_list.py b/addons/product/wizard/product_price_list.py index d9742444536..06609f12102 100644 --- a/addons/product/wizard/product_price_list.py +++ b/addons/product/wizard/product_price_list.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import api, fields, models +from odoo import api, fields, models, _ +from odoo.exceptions import UserError class product_price_list(models.TransientModel): @@ -21,6 +22,11 @@ class product_price_list(models.TransientModel): To get the date and print the report @return : return report """ + if (not self.env.user.company_id.logo): + raise UserError(_("You have to set a logo or a layout for your company.")) + elif (not self.env.user.company_id.external_report_layout): + raise UserError(_("You have to set your reports's header and footer layout.")) + datas = {'ids': self.env.context.get('active_ids', [])} res = self.read(['price_list', 'qty1', 'qty2', 'qty3', 'qty4', 'qty5']) res = res and res[0] or {} diff --git a/addons/purchase/views/account_invoice_views.xml b/addons/purchase/views/account_invoice_views.xml index 7f4fc3bf4e0..a85974931f4 100644 --- a/addons/purchase/views/account_invoice_views.xml +++ b/addons/purchase/views/account_invoice_views.xml @@ -63,8 +63,8 @@ options="{'no_create': True}"/> - - + + diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index e2f53d70819..a22b6e7c42c 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -548,9 +548,10 @@ class SaleOrder(models.Model): for tax in line.tax_id: group = tax.tax_group_id res.setdefault(group, 0.0) - amount = tax.compute_all(line.price_reduce + base_tax, quantity=line.product_uom_qty, - product=line.product_id, partner=self.partner_shipping_id)['taxes'][0]['amount'] - res[group] += amount + taxes = tax.compute_all(line.price_reduce + base_tax, quantity=line.product_uom_qty, + product=line.product_id, partner=self.partner_shipping_id)['taxes'] + for t in taxes: + res[group] += t['amount'] if tax.include_base_amount: base_tax += tax.compute_all(line.price_reduce + base_tax, quantity=1, product=line.product_id, partner=self.partner_shipping_id)['taxes'][0]['amount'] diff --git a/addons/sale_margin/models/sale_order.py b/addons/sale_margin/models/sale_order.py index b99b49c25b0..2aac5cb9936 100644 --- a/addons/sale_margin/models/sale_order.py +++ b/addons/sale_margin/models/sale_order.py @@ -59,7 +59,12 @@ class SaleOrderLine(models.Model): def _product_margin(self): for line in self: currency = line.order_id.pricelist_id.currency_id - line.margin = currency.round(line.price_subtotal - ((line.purchase_price or line.product_id.standard_price) * line.product_uom_qty)) + price = line.purchase_price + if not price: + from_cur = line.env.user.company_id.currency_id.with_context(date=line.order_id.date_order) + price = from_cur.compute(line.product_id.standard_price, currency, round=False) + + line.margin = currency.round(line.price_subtotal - (price * line.product_uom_qty)) class SaleOrder(models.Model): diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js index 2f06168fd23..906464a5a26 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -2181,7 +2181,7 @@ var BasicModel = AbstractModel.extend({ var type = record.fields[fieldName].type; var value; if (type === 'one2many' || type === 'many2many') { - if (commands[fieldName].length) { // replace localId by commands + if (commands[fieldName] && commands[fieldName].length) { // replace localId by commands changes[fieldName] = commands[fieldName]; } else { // no command -> no change for that field delete changes[fieldName]; @@ -2256,11 +2256,11 @@ var BasicModel = AbstractModel.extend({ type = fields[fieldName].type; if (type === 'many2many' || type === 'one2many') { - commands[fieldName] = []; if (!data[fieldName]) { // skip if this field is empty continue; } + commands[fieldName] = []; var list = this.localData[data[fieldName]]; if (options.changesOnly && (!list._changes || !list._changes.length)) { // if only changes are requested, skip if there is no change diff --git a/addons/web/static/tests/views/form_tests.js b/addons/web/static/tests/views/form_tests.js index 0857cb50038..e4940f5fd2d 100644 --- a/addons/web/static/tests/views/form_tests.js +++ b/addons/web/static/tests/views/form_tests.js @@ -1146,6 +1146,53 @@ QUnit.module('Views', { form.destroy(); }); + QUnit.test('onchange only send present fields value', function (assert) { + assert.expect(1); + this.data.partner.onchanges.foo = function (obj) {}; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '', + res_id: 1, + mockRPC: function (route, args) { + if (args.method === "onchange") { + assert.deepEqual(args.args[1], { + display_name: "first record", + foo: "tralala", + id: 1, + p: [[0, false, {"display_name": "valid line", "qux": 12.4}]] + }, "should send the values for the present fields"); + } + return this._super(route, args); + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + + // add a o2m row + form.$('.o_field_x2many_list_row_add a').click(); + form.$('.o_field_one2many input:first').focus(); + form.$('.o_field_one2many input:first').val('valid line').trigger('input'); + form.$('.o_field_one2many input:last').focus(); + form.$('.o_field_one2many input:last').val('12.4').trigger('input'); + + // trigger an onchange by modifying foo + form.$('input[name="foo"]:first').val("tralala").trigger('input'); + + form.destroy(); + }); + QUnit.test('evaluate in python field options', function (assert) { assert.expect(1); diff --git a/addons/web/tests/test_js.py b/addons/web/tests/test_js.py index abb4e5b9aa8..2253b1b73fc 100644 --- a/addons/web/tests/test_js.py +++ b/addons/web/tests/test_js.py @@ -10,4 +10,4 @@ class WebSuite(odoo.tests.HttpCase): at_install = False def test_01_js(self): - self.phantom_js('/web/tests?mod=web', "", "", login='admin', timeout=120) + self.phantom_js('/web/tests?mod=web', "", "", login='admin', timeout=180) diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py index 9e135847e19..7795ec9bd4d 100644 --- a/addons/website_sale/controllers/main.py +++ b/addons/website_sale/controllers/main.py @@ -143,16 +143,17 @@ class WebsiteSale(http.Controller): (variant id, [visible attribute ids], variant price, variant sale price) """ # product attributes with at least two choices - product = product.with_context(quantity=1) + quantity = product._context.get('quantity') or 1 + product = product.with_context(quantity=quantity) visible_attrs_ids = product.attribute_line_ids.filtered(lambda l: len(l.value_ids) > 1).mapped('attribute_id').ids to_currency = request.website.get_current_pricelist().currency_id attribute_value_ids = [] for variant in product.product_variant_ids: if to_currency != product.currency_id: - price = variant.currency_id.compute(variant.website_public_price, to_currency) + price = variant.currency_id.compute(variant.website_public_price, to_currency) / quantity else: - price = variant.website_public_price + price = variant.website_public_price / quantity visible_attribute_ids = [v.id for v in variant.attribute_value_ids if v.attribute_id.id in visible_attrs_ids] attribute_value_ids.append([variant.id, visible_attribute_ids, variant.website_price, price]) return attribute_value_ids diff --git a/addons/website_sale/static/src/js/website_sale.js b/addons/website_sale/static/src/js/website_sale.js index 1df1fcd0e97..da186267f57 100644 --- a/addons/website_sale/static/src/js/website_sale.js +++ b/addons/website_sale/static/src/js/website_sale.js @@ -86,23 +86,30 @@ odoo.define('website_sale.website_sale', function (require) { $(oe_website_sale).on("change", 'input[name="add_qty"]', function (event) { var product_ids = []; - var product_dom = $(".js_product .js_add_cart_variants[data-attribute_value_ids]").last(); + var product_dom = $(".js_product .js_add_cart_variants[data-attribute_value_ids]"); + var qty = $(event.target).closest('form').find('input[name="add_qty"]').val(); if (!product_dom.length) { return; } - _.each(product_dom.data("attribute_value_ids"), function(entry) { - product_ids.push(entry[0]);}); - var qty = $(event.target).closest('form').find('input[name="add_qty"]').val(); + _.each(product_dom, function (prod) { + _.each($(prod).data("attribute_value_ids"), function(entry) { + if (product_ids.indexOf(entry[0]) === -1) { + product_ids.push(entry[0]); + } + }); + }); if ($("#product_detail").length) { // display the reduction from the pricelist in function of the quantity ajax.jsonRpc("/shop/get_unit_price", 'call', {'product_ids': product_ids,'add_qty': parseInt(qty)}) .then(function (data) { - var current = product_dom.data("attribute_value_ids"); - for(var j=0; j < current.length; j++){ - current[j][2] = data[current[j][0]]; - } - product_dom.attr("data-attribute_value_ids", JSON.stringify(current)).trigger("change"); + _.each(product_dom, function (prod) { + var current = $(prod).data("attribute_value_ids"); + for(var j=0; j < current.length; j++){ + current[j][2] = data[current[j][0]]; + } + $(prod).trigger("change"); + }); }); } }); @@ -295,17 +302,18 @@ odoo.define('website_sale.website_sale', function (require) { $(oe_website_sale).on('change', 'input.js_variant_change, select.js_variant_change, ul[data-attribute_value_ids]', function (ev) { var $ul = $(ev.target).closest('.js_add_cart_variants'); var $parent = $ul.closest('.js_product'); - var $product_id = $parent.find('input.product_id').first(); - var $price = $parent.find(".oe_price:first .oe_currency_value") - .add($('#product_confirmation').find(".oe_price")); - var $default_price = $parent.find(".oe_default_price:first .oe_currency_value") - .add($('#product_confirmation').find(".oe_default_price:first .oe_currency_value")); + var $product_id = $parent.find('.product_id').first(); + var $price = $parent.find(".oe_price:first .oe_currency_value"); + var $default_price = $parent.find(".oe_default_price:first .oe_currency_value"); var $optional_price = $parent.find(".oe_optional:first .oe_currency_value"); var variant_ids = $ul.data("attribute_value_ids"); var values = []; + var unchanged_values = $parent.find('div.oe_unchanged_value_ids').data('unchanged_value_ids') || []; + $parent.find('input.js_variant_change:checked, select.js_variant_change').each(function () { values.push(+$(this).val()); }); + values = values.concat(unchanged_values); $parent.find("label").removeClass("text-muted css_not_available"); @@ -319,8 +327,10 @@ odoo.define('website_sale.website_sale', function (require) { if (variant_ids[k][3]-variant_ids[k][2]>0.01) { $default_price.closest('.oe_website_sale').addClass("discount"); $optional_price.closest('.oe_optional').show().css('text-decoration', 'line-through'); + $default_price.parent().removeClass('hidden'); } else { $optional_price.closest('.oe_optional').hide(); + $default_price.parent().addClass('hidden'); } product_id = variant_ids[k][0]; update_product_image(this, product_id); diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml index 2d25cc8d4fe..e002d81a6dd 100644 --- a/addons/website_sale/views/templates.xml +++ b/addons/website_sale/views/templates.xml @@ -455,7 +455,8 @@

Product Name