diff --git a/addons/sale/__manifest__.py b/addons/sale/__manifest__.py index cc198bd4956..ef8885f8ae4 100644 --- a/addons/sale/__manifest__.py +++ b/addons/sale/__manifest__.py @@ -83,7 +83,8 @@ This module contains all the common features of Sales Management and eCommerce. 'sale/static/tests/tours/**/*', ], 'web.qunit_suite_tests': [ - 'sale/static/tests/sales_team_dashboard_tests.js', + 'sale/static/tests/**/*', + ('remove', 'sale/static/tests/tours/**/*') ], 'web.report_assets_common': [ 'sale/static/src/scss/sale_report.scss', diff --git a/addons/sale/static/src/js/sale_product_field.js b/addons/sale/static/src/js/sale_product_field.js index dbe910960d6..255ae56b339 100644 --- a/addons/sale/static/src/js/sale_product_field.js +++ b/addons/sale/static/src/js/sale_product_field.js @@ -1,5 +1,6 @@ /** @odoo-module **/ +import { useEffect } from '@odoo/owl'; import { registry } from "@web/core/registry"; import { Many2OneField, many2OneField } from "@web/views/fields/many2one/many2one_field"; @@ -11,47 +12,27 @@ export class SaleOrderLineProductField extends Many2OneField { setup() { super.setup(); - const { update } = this; - this.update = async (value) => { - await update(value); - let newValue = false; - // NB: quick creation doesn't go through here, but through quickCreate - // below - if (value) { - if ( - Array.isArray(value[0]) && - this.props.record.data[this.props.name] != value[0] - ) { - // product (existing) - newValue = true; - } else { - // new product (Create & edit) - // value[0] is a dict of creation values - newValue = true; - } - } - if (newValue) { - if (this.relation === "product.template") { - this._onProductTemplateUpdate(); - } else { - this._onProductUpdate(); - } - } + let isMounted = false; + let isInternalUpdate = false; + const { updateRecord } = this; + this.updateRecord = (value) => { + isInternalUpdate = true; + return updateRecord.call(this, value); }; - - if (this.props.canQuickCreate) { - // HACK to make quick creation also open - // configurators if needed - this.quickCreate = async (name, params = {}) => { - await this.props.record.update({ [this.props.name]: [false, name] }); - + useEffect(value => { + if (!isMounted) { + isMounted = true; + } else if (value && isInternalUpdate) { + // we don't want to trigger product update when update comes from an external sources, + // such as an onchange, or the product configuration dialog itself if (this.relation === "product.template") { this._onProductTemplateUpdate(); } else { this._onProductUpdate(); } - }; - } + } + isInternalUpdate = false; + }, () => [Array.isArray(this.value) && this.value[0]]); } get isProductClickable() { diff --git a/addons/sale/static/tests/sale_product_field_tests.js b/addons/sale/static/tests/sale_product_field_tests.js new file mode 100644 index 00000000000..deae3805b72 --- /dev/null +++ b/addons/sale/static/tests/sale_product_field_tests.js @@ -0,0 +1,128 @@ +/** @odoo-module **/ + +import { + getFixture, patchWithCleanup, addRow, editInput, triggerEvent, click } from "@web/../tests/helpers/utils"; +import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; +import { browser } from "@web/core/browser/browser"; + +let serverData; +let target; + +QUnit.module("Fields", (hooks) => { + hooks.beforeEach(() => { + target = getFixture(); + serverData = { + models: { + 'sale.order': { + fields: { + display_name: { string: "Displayed name", type: "char" }, + order_line: { + string: "order lines", + type: "one2many", + relation: "sale.order.line", + relation_field: "order_id", + }, + }, + records: [ + { + id: 1, + display_name: "first record", + order_line: [], + }, + ], + onchanges: {}, + }, + 'sale.order.line': { + fields: { + product_template_id: { + string: "Product", + type: "many2one", + relation: "product.template", + }, + }, + records: [ + ], + }, + 'product.template': { + fields: { + display_name: { string: "Partner Type", type: "char" }, + name: { string: "Partner Type", type: "char" }, + }, + records: [ + { id: 12, display_name: "desk" }, + ], + methods: { + get_single_product_variant() { + return Promise.resolve({product_id: 12}); + } + } + }, + user: { + fields: { + name: { string: "Name", type: "char" }, + partner_ids: { + string: "one2many partners field", + type: "one2many", + relation: "partner", + relation_field: "user_id", + }, + }, + records: [ + ], + }, + }, + }; + + setupViewRegistries(); + + patchWithCleanup(browser, { + setTimeout: (fn) => fn(), + }); + }); + + QUnit.module("Sale product field"); + + QUnit.test("blurring input with incomplete text will propose to create product", async function (assert) { + + await makeView({ + type: "form", + resModel: "sale.order", + serverData, + arch: ` +
+ + + + + + + +
`, + mockRPC(route, args) { + assert.step(args.method); + } + }); + + // add a line and enter new product name + await addRow(target, ".o_field_x2many_list"); + await editInput(target, "[name='product_template_id'] input", "new product"); + + // blur input => should ask for confirmation if we want to create product + await triggerEvent(target, "[name='product_template_id'] input", "blur"); + assert.containsOnce(target, ".modal:contains(Create new product as a new Product)") + assert.verifySteps([ + "get_views", + "onchange", + "onchange", + "name_search", + ]); + + await click(target, ".modal button.btn-primary"); + assert.verifySteps([ + "name_create", + "get_single_product_variant", + ]); + + }); + +}); diff --git a/addons/sale_timesheet/static/tests/tours/sale_timesheet_tour.js b/addons/sale_timesheet/static/tests/tours/sale_timesheet_tour.js index f402185ad4c..25cc02e0d8a 100644 --- a/addons/sale_timesheet/static/tests/tours/sale_timesheet_tour.js +++ b/addons/sale_timesheet/static/tests/tours/sale_timesheet_tour.js @@ -36,6 +36,9 @@ registry.category("web_tour.tours").add('sale_timesheet_tour', { }, { trigger: '.o_field_widget[name=price_subtotal]:contains(2,500.00)', run() {}, +}, { + trigger: 'div[name="name"] textarea:propValueContains(Service Product)', + run: () => {} }, { trigger: 'button[name="action_confirm"]', content: 'Click on Confirm button to create a sale order with this quotation.',