[IMP] product: migrate product_pricelist_report to OWL

Migration of the js code of `product_pricelist_report` to OWL.

Task - 3058360

closes odoo/odoo#105341

Signed-off-by: Victor Feyens (vfe) <vfe@odoo.com>
Co-authored-by: Xavier (xlu-odoo) <xlu@odoo.com>
This commit is contained in:
Horacio Tellez
2023-02-03 05:08:43 +01:00
co-authored by Xavier
parent 6e700157d0
commit ca623471cd
10 changed files with 363 additions and 449 deletions
-1
View File
@@ -60,7 +60,6 @@ Print product labels with barcode.
'assets': {
'web.assets_backend': [
'product/static/src/js/**/*',
'product/static/src/xml/**/*',
],
'web.report_assets_common': [
'product/static/src/scss/report_label_sheet.scss',
@@ -38,7 +38,7 @@ class ProductPricelistReport(models.AbstractModel):
return {
'is_html_type': report_type == 'html',
'is_product_tmpl': is_product_tmpl,
'is_visible_title': bool(data['is_visible_title']) or False,
'display_pricelist_title': bool(data['display_pricelist_title']) or False,
'pricelist': pricelist,
'products': products_data,
'quantities': quantities,
@@ -3,33 +3,29 @@
<template id="report_pricelist_page">
<div class="container bg-white p-4 my-4">
<div class="row my-3">
<div class="col-12" t-if="is_visible_title">
<div class="row my-3" t-if="display_pricelist_title">
<div class="col-12">
<h2 t-if="is_html_type">
Pricelist:
<a href="#" class="o_action" data-model="product.pricelist" t-att-data-res-id="pricelist.id">
<t t-esc="pricelist.display_name"/>
<t t-out="pricelist.display_name"/>
</a>
</h2>
<h2 t-else="">
Pricelist: <t t-esc="pricelist.display_name"/>
Pricelist: <t t-out="pricelist.display_name"/>
</h2>
</div>
</div>
<div class="row">
<div t-att-class="'text-center' + (' offset-4' if is_html_type else ' offset-3')">
<strong>Sales Order Line Quantities (price per unit)</strong>
</div>
</div>
<div class="row">
<div class="col-12">
<table class="table table-sm">
<thead>
<th class="text-end" colspan="100%">Quantities (Price)</th>
<tr>
<th>Products</th>
<th groups="uom.group_uom">UoM</th>
<t t-foreach="quantities" t-as="qty">
<th class="text-end"><t t-esc="qty"/></th>
<th class="text-end"><t t-out="qty"/></th>
</t>
</tr>
</thead>
@@ -38,18 +34,16 @@
<tr>
<td t-att-class="is_product_tmpl and 'fw-bold' or None">
<a t-if="is_html_type" href="#" class="o_action" t-att-data-model="is_product_tmpl and 'product.template' or 'product.product'" t-att-data-res-id="product['id']">
<t t-esc="product['name']"/>
<t t-out="product['name']"/>
</a>
<t t-else="">
<t t-esc="product['name']"/>
<t t-out="product['name']"/>
</t>
</td>
<td groups="uom.group_uom">
<t t-esc="product['uom']"/>
</td>
<td groups="uom.group_uom" t-out="product['uom']"/>
<t t-foreach="quantities" t-as="qty">
<td class="text-end">
<t t-esc="product['price'][qty]" t-options='{"widget": "monetary", "display_currency": pricelist.currency_id}'/>
<t t-out="product['price'][qty]" t-options='{"widget": "monetary", "display_currency": pricelist.currency_id}'/>
</td>
</t>
</tr>
@@ -57,16 +51,14 @@
<tr t-foreach="product['variants']" t-as="variant">
<td>
<a t-if="is_html_type" href="#" class="o_action ms-4" data-model="product.product" t-att-data-res-id="variant['id']">
<t t-esc="variant['name']"/>
<t t-out="variant['name']"/>
</a>
<span t-else="" class="ms-4" t-esc="variant['name']"/>
</td>
<td groups="uom.group_uom">
<t t-esc="product['uom']"/>
<span t-else="" class="ms-4" t-out="variant['name']"/>
</td>
<td groups="uom.group_uom" t-out="product['uom']"/>
<t t-foreach="quantities" t-as="qty">
<td class="text-end">
<t t-esc="variant['price'][qty]" t-options='{"widget": "monetary", "display_currency": pricelist.currency_id}'/>
<t t-out="variant['price'][qty]" t-options='{"widget": "monetary", "display_currency": pricelist.currency_id}'/>
</td>
</t>
</tr>
@@ -0,0 +1,213 @@
/** @odoo-module **/
import { Component, markup, onRendered, onWillStart, useState } from "@odoo/owl";
import { Layout } from "@web/search/layout";
import { registry } from "@web/core/registry";
import { sprintf } from "@web/core/utils/strings";
import { useService } from "@web/core/utils/hooks";
import { useSetupAction } from "@web/webclient/actions/action_hook";
function sendCustomNotification(type, message) {
return {
type: "ir.actions.client",
tag: "display_notification",
params: {
"type": type,
"message": message
},
}
}
export class ProductPricelistReport extends Component {
setup() {
this.action = useService("action");
this.orm = useService("orm");
this.MAX_QTY = 5;
const pastState = this.props.state || {};
this.activeIds = this.props.action.context.active_ids;
this.activeModel = this.props.action.context.active_model;
this.state = useState({
displayPricelistTitle: pastState.displayPricelistTitle || false,
html: "",
pricelists: [],
_quantities: pastState.quantities || [1, 5, 10],
selectedPricelist: {},
});
onWillStart(async () => {
this.state.pricelists = await this.getPricelists()
this.state.selectedPricelist = pastState.selectedPricelist || this.pricelists[0];
this.renderHtml();
});
onRendered(() => {
this.env.config.setDisplayName(this.env._t("Pricelist Report"));
});
/*
When following the link of a product and coming back we need to keep the
precedent state:
- if the pricelist was being showed
- wich pricelist is selected at the moment
- which quantities
*/
useSetupAction({
getLocalState: () => {
return {
displayPricelistTitle: this.displayPricelistTitle,
quantities: this.quantities,
selectedPricelist: this.selectedPricelist,
};
},
});
}
// getters and setters
get displayPricelistTitle() {
return this.state.displayPricelistTitle;
}
get html() {
return this.state.html;
}
get pricelists() {
return this.state.pricelists;
}
get quantities() {
return this.state._quantities;
}
set quantities(value) {
this.state._quantities = value;
}
get reportParams() {
return {
active_model: this.activeModel || 'product.template',
active_ids: this.activeIds || [],
display_pricelist_title: this.displayPricelistTitle || '',
pricelist_id: this.selectedPricelist.id || '',
quantities: this.quantities || [1],
};
}
get selectedPricelist() {
return this.state.selectedPricelist;
}
// orm calls
getPricelists() {
return this.orm.searchRead("product.pricelist", [], ["id", "name"]);
}
async renderHtml() {
let html = await this.orm.call(
"report.product.report_pricelist", "get_html", [], {data: this.reportParams}
);
this.state.html = markup(html);
}
// events
async onClickAddQty(ev) {
ev.preventDefault(); // avoid automatic reloading of the page
if (this.quantities.length >= this.MAX_QTY) {
let message = sprintf(
this.env._t("At most %s quantities can be displayed simultaneously. Remove a selected quantity to add others."),
this.MAX_QTY
);
await this.action.doAction(sendCustomNotification("warning", message));
return;
}
const qty = parseInt($("input.add-quantity-input")[0].value);
if (qty && qty > 0) {
// Check qty already exist.
if (this.quantities.indexOf(qty) === -1) {
this.quantities.push(qty);
this.quantities = this.quantities.sort((a, b) => a - b);
this.renderHtml();
} else {
let message = sprintf(this.env._t("Quantity already present (%s)."), qty);
await this.action.doAction(sendCustomNotification("info", message));
}
} else {
await this.action.doAction(
sendCustomNotification("info", this.env._t("Please enter a positive whole number."))
);
}
}
onClickLink(ev) {
ev.preventDefault();
let classes = ev.target.getAttribute("class", "");
let resModel = ev.target.getAttribute("data-model", "");
let resId = ev.target.getAttribute("data-res-id", "");
if (classes && classes.includes("o_action") && resModel && resId) {
this.action.doAction({
type: 'ir.actions.act_window',
res_model: resModel,
res_id: parseInt(resId),
views: [[false, 'form']],
target: 'self',
});
}
}
onClickPrint() {
this.action.doAction({
type: 'ir.actions.report',
report_type: 'qweb-pdf',
report_name: 'product.report_pricelist',
report_file: 'product.report_pricelist',
data: this.reportParams,
});
}
async onClickRemoveQty(ev) {
if (this.quantities.length <= 1) {
await this.action.doAction(
sendCustomNotification("warning", this.env._t("You must leave at least one quantity."))
);
return;
}
const qty = parseInt(ev.srcElement.parentElement.childNodes[0].data);
this.quantities = this.quantities.filter(q => q !== qty);
this.renderHtml();
}
onSelectPricelist(ev) {
this.state.selectedPricelist = this.pricelists.filter(pricelist =>
pricelist.id === parseInt(ev.target.value)
)[0];
this.renderHtml();
}
onToggleDisplayPricelist() {
this.state.displayPricelistTitle = !this.displayPricelistTitle;
this.renderHtml();
}
}
ProductPricelistReport.props = {
action: { type: Object },
"*": true,
}
ProductPricelistReport.components = { Layout };
ProductPricelistReport.template = "product.ProductPricelistReport";
registry.category("actions").add("generate_pricelist_report", ProductPricelistReport);
@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<t t-name="product.ProductPricelistReport" owl="1">
<div class="o_action">
<Layout display="{ controlPanel: { 'bottom-right': false } }">
<t t-set-slot="control-panel-bottom-left">
<button t-on-click="onClickPrint" type="button" class="btn btn-primary" title="Print">Print</button>
</t>
<t t-set-slot="control-panel-top-right">
<form class="o_pricelist_report_form d-flex flex-column ">
<div class="d-flex align-items-center gap-3 w-100 ">
<div class="d-flex align-items-center">
<label class="fw-bold">Pricelist:</label>
<div class="o_select_dropdown">
<select name="pricelists"
id="pricelists"
class="o_select form-select border-0"
t-on-change="onSelectPricelist">
<option t-out="selectedPricelist.name" t-att-value="selectedPricelist.id"/>
<t t-foreach="pricelists" t-as="pricelist" t-key="pricelist.id">
<t t-if="pricelist.id != selectedPricelist.id">
<option t-out="pricelist.name" t-att-value="pricelist.id"/>
</t>
</t>
</select>
</div>
</div>
<div class="form-check m-0 p-0">
<input class="o_display_pricelist_title form-check-input ms-0 me-2"
type="checkbox"
t-att-checked="displayPricelistTitle"
t-on-click="onToggleDisplayPricelist"/>
<label class="form-check-label">Display Pricelist</label>
</div>
</div>
<div class="d-flex align-items-center gap-3 w-100">
<div class="input-group d-flex flex-nowrap w-50" style="min-width:210px;">
<span class="input-group-text fw-bold">
Quantities:
</span>
<input type="number"
class="form-control add-quantity-input"
value="1"
min="1"/>
<button class="o_add_qty btn btn-primary fa fa-plus"
type="submit"
t-on-click="onClickAddQty"
title="Add a quantity"/>
</div>
<div class="d-flex align-items-center w-50">
<span class="o_badges_list">
<t t-foreach="quantities" t-as="qty" t-key="qty">
<span class="o_field_badge o_remove_qty badge rounded-pill me-2 py-1 border " t-att-value="qty">
<t class="me-2" t-esc="qty"/>
<i class="oi oi-close ms-1 opacity-50 opacity-100-hover text-900 cursor-pointer"
title="Remove quantity"
t-on-click="onClickRemoveQty"/>
</span>
</t>
</span>
</div>
</div>
</form>
</t>
<div t-on-click="onClickLink">
<t t-out="html"/>
</div>
</Layout>
</div>
</t>
</templates>
@@ -1,317 +0,0 @@
odoo.define('product.generate_pricelist', function (require) {
'use strict';
var AbstractAction = require('web.AbstractAction');
var core = require('web.core');
var FieldMany2One = require('web.relational_fields').FieldMany2One;
var StandaloneFieldManagerMixin = require('web.StandaloneFieldManagerMixin');
var Widget = require('web.Widget');
var QWeb = core.qweb;
var _t = core._t;
var QtyTagWidget = Widget.extend({
template: 'product.report_pricelist_qty',
events: {
'click .o_remove_qty': '_onClickRemoveQty',
},
/**
* @override
*/
init: function (parent, defaulQuantities) {
this._super.apply(this, arguments);
this.quantities = defaulQuantities;
this.MAX_QTY = 5;
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* Add a quantity when add(+) button clicked.
*
* @private
*/
_onClickAddQty: function () {
if (this.quantities.length >= this.MAX_QTY) {
this.displayNotification({ message: _.str.sprintf(
_t("At most %d quantities can be displayed simultaneously. Remove a selected quantity to add others."),
this.MAX_QTY
) });
return;
}
const qty = parseInt(this.$('.o_product_qty').val());
if (qty && qty > 0) {
// Check qty already exist
if (this.quantities.indexOf(qty) === -1) {
this.quantities.push(qty);
this.quantities = this.quantities.sort((a, b) => a - b);
this.trigger_up('qty_changed', {quantities: this.quantities});
this.renderElement();
} else {
this.displayNotification({
message: _.str.sprintf(_t("Quantity already present (%d)."), qty),
type: 'info'
});
}
} else {
this.displayNotification({ message: _t("Please enter a positive whole number") });
}
},
/**
* Remove quantity.
*
* @private
* @param {jQueryEvent} ev
*/
_onClickRemoveQty: function (ev) {
const qty = parseInt($(ev.currentTarget).closest('.badge').data('qty'));
this.quantities = this.quantities.filter(q => q !== qty);
this.trigger_up('qty_changed', {quantities: this.quantities});
this.renderElement();
},
});
var GeneratePriceList = AbstractAction.extend(StandaloneFieldManagerMixin, {
hasControlPanel: true,
events: {
'click .o_action': '_onClickAction',
'submit form': '_onSubmitForm',
},
custom_events: Object.assign({}, StandaloneFieldManagerMixin.custom_events, {
field_changed: '_onFieldChanged',
qty_changed: '_onQtyChanged',
}),
/**
* @override
*/
init: function (parent, params) {
this._super.apply(this, arguments);
StandaloneFieldManagerMixin.init.call(this);
this.context = params.context;
// in case the window got refreshed
if (params.params && params.params.active_ids && typeof(params.params.active_ids === 'string')) {
try {
this.context.active_ids = params.params.active_ids.split(',').map(id => parseInt(id));
this.context.active_model = params.params.active_model;
} catch {
console.log('unable to load ids from the url fragment 🙁');
}
}
if (!this.context.active_model) {
// started without an active module, assume product templates
this.context.active_model = 'product.template';
}
this.context.quantities = [1, 5, 10];
},
/**
* @override
*/
willStart: function () {
let getPricelist;
// started without a selected pricelist in context? just get the first one
if (this.context.default_pricelist) {
getPricelist = Promise.resolve([this.context.default_pricelist]);
} else {
getPricelist = this._rpc({
model: 'product.pricelist',
method: 'search',
args: [[]],
kwargs: {limit: 1}
});
}
const fieldSetup = getPricelist.then(pricelistIds => {
return this.model.makeRecord('report.product.report_pricelist', [{
name: 'pricelist_id',
type: 'many2one',
relation: 'product.pricelist',
value: pricelistIds[0],
}]);
}).then(recordID => {
const record = this.model.get(recordID);
this.many2one = new FieldMany2One(this, 'pricelist_id', record, {
mode: 'edit',
attrs: {
can_create: false,
can_write: false,
options: {no_open: true},
},
});
this._registerWidget(recordID, 'pricelist_id', this.many2one);
});
return Promise.all([fieldSetup, this._getHtml(), this._super()]);
},
/**
* @override
*/
start: function () {
this.controlPanelProps.cp_content = this._renderComponent();
const $content = this.controlPanelProps.cp_content;
$content["$searchview"][0].querySelector('.o_is_visible_title').addEventListener('click', this._onClickVisibleTitle.bind(this));
return this._super.apply(this, arguments).then(() => {
this.$('.o_content').html(this.reportHtml);
});
},
/**
* Include the current model (template/variant) in the state to allow refreshing without losing
* the proper context.
* @override
*/
getState: function () {
return {
active_model: this.context.active_model,
};
},
getTitle: function () {
return _t('Pricelist Report');
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Returns the expected data for the report rendering call (html or pdf)
*
* @private
* @returns {Object}
*/
_prepareActionReportParams: function () {
return {
active_model: this.context.active_model,
active_ids: this.context.active_ids || '',
is_visible_title: this.context.is_visible_title || '',
pricelist_id: this.context.pricelist_id || '',
quantities: this.context.quantities || [1],
};
},
/**
* Get template to display report.
*
* @private
* @returns {Promise}
*/
_getHtml: function () {
return this._rpc({
model: 'report.product.report_pricelist',
method: 'get_html',
kwargs: {
data: this._prepareActionReportParams(),
context: this.context,
},
}).then(result => {
this.reportHtml = result;
});
},
/**
* Reload report.
*
* @private
* @returns {Promise}
*/
_reload: function () {
return this._getHtml().then(() => {
this.$('.o_content').html(this.reportHtml);
});
},
/**
* Render search view and print button.
*
* @private
*/
_renderComponent: function () {
const $buttons = $('<button>', {
class: 'btn btn-primary',
text: _t("Print"),
}).on('click', this._onClickPrint.bind(this));
const $searchview = $(QWeb.render('product.report_pricelist_search'));
this.many2one.appendTo($searchview.find('.o_pricelist'));
this.qtyTagWidget = new QtyTagWidget(this, this.context.quantities);
this.qtyTagWidget.replace($searchview.find('.o_product_qty'));
return { $buttons, $searchview };
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* Checkbox is checked, the report title will show.
*
* @private
* @param {Event} ev
*/
_onClickVisibleTitle(ev) {
this.context.is_visible_title = ev.currentTarget.checked;
this._reload();
},
/**
* Open form view of particular record when link clicked.
*
* @private
* @param {jQueryEvent} ev
*/
_onClickAction: function (ev) {
ev.preventDefault();
this.do_action({
type: 'ir.actions.act_window',
res_model: $(ev.currentTarget).data('model'),
res_id: $(ev.currentTarget).data('res-id'),
views: [[false, 'form']],
target: 'self',
});
},
/**
* Print report in PDF when button clicked.
*
* @private
*/
_onClickPrint: function () {
return this.do_action({
type: 'ir.actions.report',
report_type: 'qweb-pdf',
report_name: 'product.report_pricelist',
report_file: 'product.report_pricelist',
data: this._prepareActionReportParams(),
});
},
/**
* Reload report when pricelist changed.
*
* @override
*/
_onFieldChanged: function (event) {
this.context.pricelist_id = event.data.changes.pricelist_id.id;
StandaloneFieldManagerMixin._onFieldChanged.apply(this, arguments);
this._reload();
},
/**
* Reload report when quantities changed.
*
* @private
* @param {OdooEvent} ev
* @param {integer[]} event.data.quantities
*/
_onQtyChanged: function (ev) {
this.context.quantities = ev.data.quantities;
this._reload();
},
_onSubmitForm: function (ev) {
ev.preventDefault();
ev.stopPropagation();
this.qtyTagWidget._onClickAddQty();
},
});
core.action_registry.add('generate_pricelist', GeneratePriceList);
return {
GeneratePriceList,
QtyTagWidget
};
});
@@ -1,45 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<t t-name="product.report_pricelist_qty">
<span>
<div class="input-group flex-nowrap w-75">
<input type="number" name="qty_to_add" class="o_input o_product_qty form-control text-end w-auto" value="1" min="1"/>
<button class="btn btn-secondary o_add_qty text-end form-control" type="submit" title="Add a quantity">
<i class="fa fa-plus"/>
</button>
</div>
<span class="o_badges">
<t t-set="quantities" t-value="widget.quantities"/>
<t t-call="product.report_pricelist_qty_badges"/>
</span>
</span>
</t>
<t t-name="product.report_pricelist_search">
<form class="d-flex justify-content-around align-items-center o_pricelist_report_form">
<div>
<label class="fw-bold">Pricelist:</label>
<span class="o_pricelist"/>
</div>
<div class="d-flex align-items-center">
<label class="fw-bold mb-4" for="qty_to_add">Quantities:</label>
<div class="o_product_qty"/>
</div>
<div class="form-check">
<input class="form-check-input o_is_visible_title ms-2" type="checkbox"/>
<label class="form-check-label">Display Pricelist</label>
</div>
</form>
</t>
<t t-name="product.report_pricelist_qty_badges">
<t t-foreach="quantities" t-as="qty">
<span class="badge rounded-pill border" t-att-data-qty="qty">
<t t-esc="qty"/>
<i class="fa fa-close o_remove_qty" title="Remove quantity"/>
</span>
</t>
</t>
</templates>
@@ -1,15 +1,22 @@
odoo.define('product.pricelist.report.tests', function (require) {
"use strict";
const GeneratePriceList = require('product.generate_pricelist').GeneratePriceList;
const testUtils = require('web.test_utils');
/** @odoo-module **/
import {
click,
editSelect,
editInput,
findElement,
getFixture,
patchWithCleanup
} from "@web/../tests/helpers/utils";
import { createWebClient, doAction } from "@web/../tests/webclient/helpers";
import { ProductPricelistReport } from "@product/js/pricelist_report/product_pricelist_report";
const { createWebClient, doAction } = require('@web/../tests/webclient/helpers');
const { getFixture, patchWithCleanup } = require("@web/../tests/helpers/utils");
let serverData;
QUnit.module('Product Pricelist', {
QUnit.module('Product Pricelist Report', {
beforeEach: function () {
this.data = {
'product.product': {
fields: {
@@ -26,9 +33,11 @@ QUnit.module('Product Pricelist', {
},
records: [{
id: 1,
name: "Public Pricelist",
display_name: "Public Pricelist"
}, {
id: 2,
name: "Test",
display_name: "Test"
}]
}
@@ -37,20 +46,21 @@ QUnit.module('Product Pricelist', {
},
}, function () {
QUnit.test('Pricelist Client Action', async function (assert) {
assert.expect(23);
assert.expect(18);
let Qty = [1, 5, 10]; // default quantities
patchWithCleanup(GeneratePriceList.prototype, {
_onFieldChanged: function (event) {
assert.step('field_changed');
return this._super.apply(this, arguments);
},
_onQtyChanged: function (event) {
assert.deepEqual(event.data.quantities, Qty.sort((a, b) => a - b), "changed quantity should be same.");
assert.step('qty_changed');
return this._super.apply(this, arguments);
},
});
patchWithCleanup(ProductPricelistReport.prototype, {
onSelectPricelist: function (event) {
assert.step('pricelist_changed');
this._super(...arguments)
},
onClickAddQty: function (event) {
assert.deepEqual(this.quantities, Qty.sort((a, b) => a - b), "changed quantity should be same.");
assert.step('qty_added');
this._super(...arguments);
},
});
const mockRPC = (route, args) => {
if (route === '/web/dataset/call_kw/report.product.report_pricelist/get_html') {
return Promise.resolve("");
@@ -61,79 +71,67 @@ QUnit.module('Product Pricelist', {
const webClient = await createWebClient({ serverData, mockRPC });
await doAction(webClient, {
id: 1,
name: 'Generate Pricelist',
tag: 'generate_pricelist',
name: 'Generate Pricelist Report',
tag: 'generate_pricelist_report',
type: 'ir.actions.client',
context: {
'default_pricelist': 1,
'active_ids': [42],
'active_id': 42,
'active_model': 'product.product'
}
});
const selectElement = findElement(target, 'select');
const badgesElement = findElement(target, '.o_badges_list');
const inputElement = findElement(target, '.add-quantity-input');
const addQtyButton = findElement(target, '.o_add_qty');
// checking default pricelist
assert.strictEqual($(target).find('.o_field_many2one input').val(), "Public Pricelist",
"should have default pricelist");
assert.strictEqual(selectElement.children[0].text, "Public Pricelist", "should have default pricelist");
// changing pricelist
await testUtils.fields.many2one.clickOpenDropdown("pricelist_id");
await testUtils.fields.many2one.clickItem("pricelist_id", "Test");
await editSelect(selectElement, '', 2);
// check wherther pricelist value has been updated or not. along with that check default quantities should be there.
assert.strictEqual($(target).find('.o_field_many2one input').val(), "Test",
// check wherther pricelist value has been updated or not
assert.strictEqual(selectElement.children[0].text, "Test",
"After pricelist change, the pricelist_id field should be updated");
assert.strictEqual($(target).find('.o_badges > .badge').length, 3,
"There should be 3 default Quantities");
// check default quantities should be there
assert.strictEqual(badgesElement.children.length, 3, "There should be 3 default quantities");
// existing quantity can not be added.
await testUtils.dom.click($(target).find('.o_add_qty'));
await click(addQtyButton);
let notificationElement = document.body.querySelector('.o_notification_manager .o_notification');
assert.strictEqual(notificationElement.querySelector('.o_notification_content').textContent,
"Quantity already present (1).", "Existing Quantity can not be added");
assert.hasClass(notificationElement, "border-info");
// adding few more quantities to check.
$(target).find('.o_product_qty').val(2);
await editInput(inputElement, '', 2);
await click(addQtyButton);
Qty.push(2);
await testUtils.dom.click($(target).find('.o_add_qty'));
$(target).find('.o_product_qty').val(3);
await editInput(inputElement, '', 3);
await click(addQtyButton);
Qty.push(3);
await testUtils.dom.click($(target).find('.o_add_qty'));
// should not be added more then 5 quantities.
$(target).find('.o_product_qty').val(4);
await testUtils.dom.click($(target).find('.o_add_qty'));
// check quantities were added
assert.strictEqual(badgesElement.children.length, 5, "There should be 5 different quantities");
// no more than 5 quantities can be used at a time
await editInput(inputElement, '', 4);
await click(addQtyButton);
notificationElement = document.body.querySelector('.o_notification_manager .o_notification:nth-child(2)');
assert.strictEqual(notificationElement.querySelector('.o_notification_content').textContent,
"At most 5 quantities can be displayed simultaneously. Remove a selected quantity to add others.",
"Can not add more then 5 quantities");
assert.hasClass(notificationElement, "border-warning");
// removing all the quantities should work
Qty.pop(10);
await testUtils.dom.click($(target).find('.o_badges .badge:contains("10") .o_remove_qty'));
Qty.pop(5);
await testUtils.dom.click($(target).find('.o_badges .badge:contains("5") .o_remove_qty'));
Qty.pop(3);
await testUtils.dom.click($(target).find('.o_badges .badge:contains("3") .o_remove_qty'));
Qty.pop(2);
await testUtils.dom.click($(target).find('.o_badges .badge:contains("2") .o_remove_qty'));
Qty.pop(1);
await testUtils.dom.click($(target).find('.o_badges .badge:contains("1") .o_remove_qty'));
assert.verifySteps([
'field_changed',
'qty_changed',
'qty_changed',
'qty_changed',
'qty_changed',
'qty_changed',
'qty_changed',
'qty_changed'
'pricelist_changed',
'qty_added',
'qty_added',
'qty_added',
'qty_added',
]);
});
}
);
});
@@ -170,7 +170,7 @@ ctx.update({'default_pricelist': env['product.pricelist'].search([], limit=1).id
action = {
'name': 'Pricelist Report',
'type': 'ir.actions.client',
'tag': 'generate_pricelist',
'tag': 'generate_pricelist_report',
'context': ctx,
}
</field>
+2 -2
View File
@@ -482,7 +482,7 @@
</record>
<record id="action_product_price_list_report" model="ir.actions.server">
<field name="name">Generate Pricelist</field>
<field name="name">Generate Pricelist Report</field>
<field name="groups_id" eval="[(4, ref('group_product_pricelist'))]"/>
<field name="model_id" ref="product.model_product_product"/>
<field name="binding_model_id" ref="product.model_product_product"/>
@@ -493,7 +493,7 @@ ctx.update({'default_pricelist': env['product.pricelist'].search([], limit=1).id
action = {
'name': 'Pricelist Report',
'type': 'ir.actions.client',
'tag': 'generate_pricelist',
'tag': 'generate_pricelist_report',
'context': ctx,
}
</field>