Eliminate Python dependency: embed frontend assets in odoo-go
- Copy all OWL frontend assets (JS/CSS/XML/fonts/images) into frontend/ directory (2925 files, 43MB) — no more runtime reads from Python Odoo - Replace OdooAddonsPath config with FrontendDir pointing to local frontend/ - Rewire bundle.go, static.go, templates.go, webclient.go to read from frontend/ instead of external Python Odoo addons directory - Auto-detect frontend/ and build/ dirs relative to binary in main.go - Delete obsolete Python helper scripts (tools/*.py) The Go server is now fully self-contained: single binary + frontend/ folder. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
68
frontend/stock/static/tests/counted_quantity_widget.test.js
Normal file
68
frontend/stock/static/tests/counted_quantity_widget.test.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import { defineMailModels } from "@mail/../tests/mail_test_helpers";
|
||||
import { expect, test } from "@odoo/hoot";
|
||||
import { contains, defineModels, fields, models, mountView } from "@web/../tests/web_test_helpers";
|
||||
|
||||
class Quant extends models.Model {
|
||||
quantity = fields.Float();
|
||||
inventory_quantity = fields.Float({
|
||||
string: "Counted quantity",
|
||||
onChange: (quant) => {
|
||||
quant.inventory_diff_quantity = quant.inventory_quantity - quant.quantity;
|
||||
},
|
||||
});
|
||||
inventory_quantity_set = fields.Boolean({
|
||||
string: "Inventory quantity set",
|
||||
});
|
||||
inventory_diff_quantity = fields.Float({ string: "Difference" });
|
||||
|
||||
_records = [{ id: 1, quantity: 50 }];
|
||||
}
|
||||
defineModels([Quant]);
|
||||
defineMailModels();
|
||||
|
||||
test("Test changing the inventory quantity with the widget", async function () {
|
||||
await mountView({
|
||||
type: "list",
|
||||
resModel: "quant",
|
||||
arch: `<list editable="bottom">
|
||||
<field name="quantity"/>
|
||||
<field name="inventory_quantity" widget="counted_quantity_widget"/>
|
||||
<field name="inventory_quantity_set"/>
|
||||
<field name="inventory_diff_quantity"/>
|
||||
</list>
|
||||
`,
|
||||
});
|
||||
|
||||
await contains("td.o_counted_quantity_widget_cell").click();
|
||||
await contains("td.o_counted_quantity_widget_cell input").edit("23");
|
||||
await contains("td[name=inventory_diff_quantity]").click();
|
||||
|
||||
expect("td[name=inventory_diff_quantity] div input").toHaveValue(-27);
|
||||
expect("td[name=inventory_quantity_set] div input").toBeChecked();
|
||||
|
||||
await contains("td.o_counted_quantity_widget_cell").click();
|
||||
await contains("td.o_counted_quantity_widget_cell input").edit("40.5");
|
||||
await contains("td[name=inventory_diff_quantity]").click();
|
||||
|
||||
expect("td[name=inventory_diff_quantity] div input").toHaveValue(-9.5);
|
||||
});
|
||||
|
||||
test("Test setting the inventory quantity to its default value of 0", async function () {
|
||||
await mountView({
|
||||
type: "list",
|
||||
resModel: "quant",
|
||||
arch: `<list editable="bottom">
|
||||
<field name="quantity"/>
|
||||
<field name="inventory_quantity" widget="counted_quantity_widget"/>
|
||||
<field name="inventory_quantity_set"/>
|
||||
<field name="inventory_diff_quantity"/>
|
||||
</list>
|
||||
`,
|
||||
});
|
||||
|
||||
await contains("td.o_counted_quantity_widget_cell").click();
|
||||
await contains("td.o_counted_quantity_widget_cell input").edit("0");
|
||||
await contains("td[name=inventory_diff_quantity]").click();
|
||||
|
||||
expect("td[name=inventory_diff_quantity] div input").toHaveValue(-50);
|
||||
});
|
||||
187
frontend/stock/static/tests/inventory_report_list.test.js
Normal file
187
frontend/stock/static/tests/inventory_report_list.test.js
Normal file
@@ -0,0 +1,187 @@
|
||||
import { defineMailModels } from "@mail/../tests/mail_test_helpers";
|
||||
import { expect, test } from "@odoo/hoot";
|
||||
import { animationFrame, queryOne } from "@odoo/hoot-dom";
|
||||
import {
|
||||
contains,
|
||||
defineModels,
|
||||
fields,
|
||||
MockServer,
|
||||
models,
|
||||
mountView,
|
||||
onRpc,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
const { DateTime } = luxon;
|
||||
|
||||
const arch = `
|
||||
<list editable="top" js_class="inventory_report_list">
|
||||
<field name="name"/>
|
||||
<field name="age"/>
|
||||
<field name="job"/>
|
||||
<field name="create_date" invisible="1"/>
|
||||
<field name="write_date" invisible="1"/>
|
||||
</list>
|
||||
`;
|
||||
|
||||
const setup_date = "2022-01-03 08:03:44";
|
||||
|
||||
onRpc("person", "web_save", ({ args }) => {
|
||||
// simulate 'stock.quant' create function which can return existing record
|
||||
const values = args[1];
|
||||
const existingRecord = MockServer.env.person.find((p) => p.name === values.name);
|
||||
if (existingRecord) {
|
||||
values.create_date = existingRecord.create_date;
|
||||
values.write_date = DateTime.now().toSQL();
|
||||
return [Object.assign(existingRecord, values)];
|
||||
}
|
||||
});
|
||||
|
||||
class Person extends models.Model {
|
||||
name = fields.Char();
|
||||
age = fields.Integer();
|
||||
job = fields.Char({ string: "Profession" });
|
||||
create_date = fields.Datetime({ string: "Created on" });
|
||||
write_date = fields.Datetime({ string: "Last Updated on" });
|
||||
|
||||
_records = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Daniel Fortesque",
|
||||
age: 32,
|
||||
job: "Soldier",
|
||||
create_date: setup_date,
|
||||
write_date: setup_date,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Samuel Oak",
|
||||
age: 64,
|
||||
job: "Professor",
|
||||
create_date: setup_date,
|
||||
write_date: setup_date,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Leto II Atreides",
|
||||
age: 128,
|
||||
job: "Emperor",
|
||||
create_date: setup_date,
|
||||
write_date: setup_date,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
defineModels([Person]);
|
||||
defineMailModels();
|
||||
|
||||
test("Create new record correctly", async function () {
|
||||
await mountView({
|
||||
type: "list",
|
||||
resModel: "person",
|
||||
arch,
|
||||
context: {
|
||||
inventory_mode: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Check we have initially 3 records
|
||||
expect(".o_data_row").toHaveCount(3);
|
||||
|
||||
// Create a new line...
|
||||
await contains(".o_control_panel_main_buttons .o_list_button_add").click();
|
||||
await contains("[name=name] input").edit("Bilou", { confirm: false });
|
||||
await contains("[name=age] input").edit("24", { confirm: false });
|
||||
await contains(".o_control_panel_main_buttons .o_list_button_save").click();
|
||||
|
||||
// Check new record is in the list
|
||||
expect(".o_data_row").toHaveCount(4);
|
||||
});
|
||||
|
||||
test("Don't duplicate record", async function () {
|
||||
await mountView({
|
||||
type: "list",
|
||||
resModel: "person",
|
||||
arch,
|
||||
context: {
|
||||
inventory_mode: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Check we have initially 3 records
|
||||
expect(".o_data_row").toHaveCount(3);
|
||||
|
||||
// Create a new line for an existing record...
|
||||
await contains(".o_control_panel_main_buttons .o_list_button_add").click();
|
||||
await contains("[name=name] input").edit("Leto II Atreides", { confirm: false });
|
||||
await contains("[name=age] input").edit("72", { confirm: false });
|
||||
await contains(".o_control_panel_main_buttons .o_list_button_save").click();
|
||||
|
||||
expect(".o_data_row").toHaveCount(3, { message: "should still have 3 records" });
|
||||
expect(".o_data_row:eq(2) .o_list_number").toHaveText("72", {
|
||||
message: "The age field must be updated",
|
||||
});
|
||||
await animationFrame();
|
||||
expect(".o_notification").toHaveCount(1);
|
||||
expect(".o_notification .o_notification_body").toHaveText(
|
||||
"This record already exists. You tried to create a record that already exists. The existing record was modified instead."
|
||||
);
|
||||
});
|
||||
|
||||
test("Work in grouped list", async function () {
|
||||
await mountView({
|
||||
type: "list",
|
||||
resModel: "person",
|
||||
arch,
|
||||
context: {
|
||||
inventory_mode: true,
|
||||
},
|
||||
groupBy: ["job"], // Groups are Emperor, Professor, Soldier
|
||||
});
|
||||
|
||||
// Open 'Professor' group
|
||||
await contains(".o_group_header:eq(1)").click();
|
||||
|
||||
// Check we have only 1 record...
|
||||
expect(".o_data_row").toHaveCount(1);
|
||||
|
||||
// Create a new record...
|
||||
await contains(".o_group_field_row_add a").click();
|
||||
await contains("[name=name] input").edit("Del Tutorial", { confirm: false });
|
||||
await contains("[name=age] input").edit("32", { confirm: false });
|
||||
await contains(".o_control_panel_main_buttons .o_list_button_save").click();
|
||||
// Check we have 2 records...
|
||||
expect(".o_data_row").toHaveCount(2);
|
||||
|
||||
// Create an existing record...
|
||||
await contains(".o_group_field_row_add a").click();
|
||||
await contains("[name=name] input").edit("Samuel Oak", { confirm: false });
|
||||
await contains("[name=age] input").edit("55", { confirm: false });
|
||||
await contains(".o_control_panel_main_buttons .o_list_button_save").click();
|
||||
// Check we still have 2 records...
|
||||
expect(".o_data_row").toHaveCount(2);
|
||||
|
||||
// Create an existing but not displayed record...
|
||||
await contains(".o_group_field_row_add a").click();
|
||||
await contains("[name=name] input").edit("Daniel Fortesque", { confirm: false });
|
||||
await contains("[name=age] input").edit("55", { confirm: false });
|
||||
await contains("[name=job] input").edit("Soldier", { confirm: false }); // let it in its original group
|
||||
await contains(".o_control_panel_main_buttons .o_list_button_save").click();
|
||||
// Check we have 3 records...
|
||||
expect(".o_data_row").toHaveCount(3);
|
||||
|
||||
// Opens 'Soldier' group
|
||||
await contains(".o_group_header:eq(2)").click();
|
||||
|
||||
// Check 'original' record has been updated...
|
||||
// : Daniel Fortesque is in record 0 for group Soldier and in record 3 for group Professor
|
||||
expect('.o_data_row:eq(0) [name="age"]').toHaveText("55");
|
||||
|
||||
// Edit the freshly created record...
|
||||
await contains(".o_data_row:eq(3) .o_field_cell").click();
|
||||
await contains("[name=age] input").edit("66");
|
||||
|
||||
// Check both records have been updated...
|
||||
expect(queryOne('.o_data_row:eq(0) [name="age"]').textContent).toBe(
|
||||
queryOne('.o_data_row:eq(3) [name="age"]').textContent
|
||||
);
|
||||
});
|
||||
35
frontend/stock/static/tests/popover_widget.test.js
Normal file
35
frontend/stock/static/tests/popover_widget.test.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { defineMailModels } from "@mail/../tests/mail_test_helpers";
|
||||
import { expect, test } from "@odoo/hoot";
|
||||
import { contains, defineModels, fields, models, mountView } from "@web/../tests/web_test_helpers";
|
||||
|
||||
class Partner extends models.Model {
|
||||
json_data = fields.Char();
|
||||
|
||||
_records = [
|
||||
{
|
||||
id: 1,
|
||||
json_data:
|
||||
'{"color": "text-danger", "msg": "var that = self // why not?", "title": "JS Master"}',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
defineModels([Partner]);
|
||||
defineMailModels();
|
||||
|
||||
test("Test creation/usage form popover widget", async () => {
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
arch: `
|
||||
<form>
|
||||
<field name="json_data" widget="popover_widget"/>
|
||||
</form>`,
|
||||
resId: 1,
|
||||
});
|
||||
expect(".popover").toHaveCount(0);
|
||||
expect(".fa-info-circle.text-danger").toHaveCount(1);
|
||||
await contains(".fa-info-circle.text-danger").click();
|
||||
expect(".popover").toHaveCount(1);
|
||||
expect(".popover").toHaveText("JS Master\nvar that = self // why not?");
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import { defineMailModels } from "@mail/../tests/mail_test_helpers";
|
||||
import { expect, test } from "@odoo/hoot";
|
||||
import { defineActions, getService, mountWithCleanup, onRpc } from "@web/../tests/web_test_helpers";
|
||||
import { WebClient } from "@web/webclient/webclient";
|
||||
|
||||
defineActions([
|
||||
{
|
||||
id: 42,
|
||||
name: "Stock report",
|
||||
tag: "stock_report_generic",
|
||||
type: "ir.actions.client",
|
||||
context: {},
|
||||
params: {},
|
||||
},
|
||||
]);
|
||||
defineMailModels();
|
||||
|
||||
test("Rendering with no lines", async function () {
|
||||
onRpc("get_main_lines", () => []);
|
||||
await mountWithCleanup(WebClient);
|
||||
|
||||
await getService("action").doAction(42);
|
||||
expect(".o_stock_reports_page").toHaveText("No operation made on this lot.");
|
||||
});
|
||||
120
frontend/stock/static/tests/tours/stock_flow_tour.js
Normal file
120
frontend/stock/static/tests/tours/stock_flow_tour.js
Normal file
@@ -0,0 +1,120 @@
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("web_tour.tours").add("test_basic_stock_flow_with_minimal_access_rights", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: ".o_menuitem[href='/odoo/inventory']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "button[data-menu-xmlid='stock.menu_stock_warehouse_mgmt']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o-dropdown-item[data-menu-xmlid='stock.in_picking']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "check that at least one picking is present in the view",
|
||||
trigger: ".o_stock_list_view_view .o_data_row",
|
||||
},
|
||||
{
|
||||
trigger: ".o_list_button_add",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_input[id=partner_id_0]",
|
||||
run: "edit Test Partner",
|
||||
},
|
||||
{
|
||||
trigger: ".dropdown-item:contains('Test Partner')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_field_x2many_list_row_add > a",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_data_row .o_input",
|
||||
run: "edit Test Product",
|
||||
},
|
||||
{
|
||||
trigger: ".dropdown-item:contains('Test Product')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_data_cell[name=product_uom_qty]",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_data_cell[name=product_uom_qty] .o_input",
|
||||
run: "edit 1",
|
||||
},
|
||||
{
|
||||
trigger: "button[name=action_confirm]",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "button[name=button_validate]",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_arrow_button_current:contains(Done)",
|
||||
},
|
||||
{
|
||||
trigger: "button[data-menu-xmlid='stock.menu_stock_warehouse_mgmt']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o-dropdown-item[data-menu-xmlid='stock.out_picking']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "check that at least one picking is present in the view",
|
||||
trigger: ".o_stock_list_view_view .o_data_row",
|
||||
},
|
||||
{
|
||||
trigger: "button:contains(New)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_input[id=partner_id_0]",
|
||||
run: "edit Test Partner",
|
||||
},
|
||||
{
|
||||
trigger: ".dropdown-item:contains('Test Partner')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_field_x2many_list_row_add > a",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_data_row .o_input",
|
||||
run: "edit Test Product",
|
||||
},
|
||||
{
|
||||
trigger: ".dropdown-item:contains('Test Product')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_data_cell[name=product_uom_qty]",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_data_cell[name=product_uom_qty] .o_input",
|
||||
run: "edit 1",
|
||||
},
|
||||
{
|
||||
trigger: "button[name=action_confirm]",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "button[name=button_validate]",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_arrow_button_current:contains(Done)",
|
||||
},
|
||||
],
|
||||
});
|
||||
526
frontend/stock/static/tests/tours/stock_picking_tour.js
Normal file
526
frontend/stock/static/tests/tours/stock_picking_tour.js
Normal file
@@ -0,0 +1,526 @@
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("web_tour.tours").add('test_generate_serial_1', { steps: () => [
|
||||
{
|
||||
trigger: '.o_field_x2many_list_row_add > a',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_field_widget[name=product_id] input",
|
||||
run: "edit Serial",
|
||||
},
|
||||
{
|
||||
trigger: ".ui-menu-item > a:contains('Product Serial')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".btn-primary[name=action_confirm]",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "button:contains('Details')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "h4:contains('Detailed Operations')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: '.o_widget_generate_serials > button',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".modal h4:contains('Generate Serial numbers')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".modal div[name=next_serial] input",
|
||||
run: "edit serial_n_1",
|
||||
},
|
||||
{
|
||||
trigger: ".modal div[name=next_serial_count] input",
|
||||
run: "edit 5 && click body",
|
||||
},
|
||||
{
|
||||
trigger: ".modal .btn-primary:contains('Generate')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "span[data-tooltip=Quantity]:contains('5')",
|
||||
run: () => {
|
||||
const nbLines = document.querySelectorAll(".o_field_cell[name=lot_name]").length;
|
||||
if (nbLines !== 5){
|
||||
console.error("wrong number of move lines generated. " + nbLines + " instead of 5");
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: ".modal button:contains(save)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "body:not(:has(.modal))",
|
||||
},
|
||||
{
|
||||
trigger: ".o_optional_columns_dropdown_toggle",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: 'input[name="picked"]',
|
||||
content: 'Check the picked field to display the column on the list view.',
|
||||
run: function (actions) {
|
||||
if (!this.anchor.checked) {
|
||||
actions.click();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: ".o_data_cell[name=picked]",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_field_widget[name=picked] input",
|
||||
run: function (actions) {
|
||||
if (!this.anchor.checked) {
|
||||
actions.click();
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
trigger: ".btn-primary[name=button_validate]",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_control_panel_actions button:contains('Traceability')",
|
||||
},
|
||||
]});
|
||||
|
||||
registry.category("web_tour.tours").add('test_generate_serial_2', { steps: () => [
|
||||
{
|
||||
trigger: '.o_field_x2many_list_row_add > a',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_field_widget[name=product_id] input",
|
||||
run: "edit Lot",
|
||||
},
|
||||
{
|
||||
trigger: ".ui-menu-item > a:contains('Product Lot 1')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_field_widget[name=product_uom_qty] input",
|
||||
run: "edit 100",
|
||||
},
|
||||
{
|
||||
trigger: ".btn-primary[name=action_confirm]",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "button:contains('Details')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".modal h4:contains('Detailed Operations')",
|
||||
run: "click",
|
||||
},
|
||||
// We generate lots for a first batch of 50 products
|
||||
{
|
||||
trigger: ".modal .o_widget_generate_serials > button",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".modal h4:contains('Generate Lot numbers')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".modal div[name=next_serial] input",
|
||||
run: "edit lot_n_1_1",
|
||||
},
|
||||
{
|
||||
trigger: ".modal div[name=next_serial_count] input",
|
||||
run() {
|
||||
//input type number not supported by tour helpers.
|
||||
this.anchor.value = "7.5";
|
||||
}
|
||||
},
|
||||
{
|
||||
trigger: ".modal div[name=total_received] input",
|
||||
run: "edit 50",
|
||||
},
|
||||
{
|
||||
trigger: ".modal .modal-footer button.btn-primary:contains(Generate)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".modal span[data-tooltip=Quantity]:contains(50)",
|
||||
run: () => {
|
||||
const nbLines = document.querySelectorAll(".o_field_cell[name=lot_name]").length;
|
||||
if (nbLines !== 7){
|
||||
console.error("wrong number of move lines generated. " + nbLines + " instead of 7");
|
||||
}
|
||||
},
|
||||
},
|
||||
// We generate lots for the last 50 products
|
||||
{
|
||||
trigger: ".modal .o_widget_generate_serials > button",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".modal h4:contains('Generate Lot numbers')",
|
||||
},
|
||||
{
|
||||
trigger: ".modal div[name=next_serial] input",
|
||||
run: "edit lot_n_2_1",
|
||||
},
|
||||
{
|
||||
trigger: ".modal div[name=next_serial_count] input",
|
||||
run: "edit 13",
|
||||
},
|
||||
{
|
||||
trigger: ".modal div[name=total_received] input",
|
||||
run: "edit 50",
|
||||
},
|
||||
{
|
||||
trigger: ".modal div[name=keep_lines] input",
|
||||
run: "check",
|
||||
},
|
||||
{
|
||||
trigger: ".modal .modal-footer button.btn-primary:contains(Generate)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".modal span[data-tooltip=Quantity]:contains(100)",
|
||||
run: () => {
|
||||
const nbLines = document.querySelectorAll(".o_field_cell[name=lot_name]").length;
|
||||
if (nbLines !== 11){
|
||||
console.error("wrong number of move lines generated. " + nbLines + " instead of 11");
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: ".modal .o_form_button_save",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "body:not(:has(.modal))",
|
||||
},
|
||||
{
|
||||
trigger: ".o_optional_columns_dropdown_toggle",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "input[name='picked']",
|
||||
content: "Check the picked field to display the column on the list view.",
|
||||
run: function (actions) {
|
||||
if (!this.anchor.checked) {
|
||||
actions.click();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: ".o_data_cell[name=picked]",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_field_widget[name=picked] input",
|
||||
run: function (actions) {
|
||||
if (!this.anchor.checked) {
|
||||
actions.click();
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
trigger: ".btn-primary[name=button_validate]",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_control_panel_actions button:contains('Traceability')",
|
||||
},
|
||||
]});
|
||||
|
||||
registry.category('web_tour.tours').add('test_inventory_adjustment_apply_all', { steps: () => [
|
||||
{
|
||||
trigger: '.o_list_button_add',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: 'div[name=product_id] input',
|
||||
run: "edit Product 1",
|
||||
},
|
||||
{
|
||||
trigger: '.ui-menu-item > a:contains("Product 1")',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: 'div[name=inventory_quantity] input',
|
||||
run: "edit 123",
|
||||
},
|
||||
// Unfocus to show the "New" button again
|
||||
{
|
||||
trigger: '.o_searchview_input_container',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: '.o_list_button_add',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: 'div[name=product_id] input',
|
||||
run: "edit Product 2",
|
||||
},
|
||||
{
|
||||
trigger: '.ui-menu-item > a:contains("Product 2")',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: 'div[name=inventory_quantity] input',
|
||||
run: "edit 456",
|
||||
},
|
||||
{
|
||||
trigger: 'button[name=action_apply_all]',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: '.modal-content button[name=action_apply]',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "body:not(:has(.modal))",
|
||||
},
|
||||
{
|
||||
content: "Check that all quants were applied.",
|
||||
trigger: "body:not(:has(button[name=action_apply_inventory]))",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_add_new_line_in_detailled_op", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: ".o_list_view.o_field_x2many .o_data_row button[name='action_show_details']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".modal-content",
|
||||
},
|
||||
{
|
||||
trigger: ".modal-content .o_field_x2many_list_row_add > a",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Pick LOT001 to create a move line with a quantity of 0.00",
|
||||
trigger: ".o_data_row .o_data_cell[name=lot_id]:contains(LOT001)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "check that the move contains three lines",
|
||||
trigger:
|
||||
".modal-content:has(.modal-header .modal-title:contains(Detailed Operations)) .o_data_row:nth-child(3)",
|
||||
},
|
||||
{
|
||||
content: "Check that the first line is associated with LOT001 for a quantity of 0.00",
|
||||
trigger:
|
||||
".modal-content .o_data_row:has(.o_field_pick_from input:value(WH/Stock - LOT001)):has(.o_field_float[name=quantity] input:value(0.00))",
|
||||
},
|
||||
{
|
||||
trigger: ".modal-content .o_field_x2many_list_row_add > a",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "LOT001 should not appear as it is not available",
|
||||
trigger: ".modal-header .modal-title:contains(Add line: Product Lot)",
|
||||
run: () => {
|
||||
const lines = document.querySelectorAll(".o_data_row .o_data_cell[name=lot_id]");
|
||||
if (lines.length !== 2) {
|
||||
console.error(
|
||||
"Wrong number of available quants: " + lines.length + " instead of 2."
|
||||
);
|
||||
}
|
||||
const lineLOT001 = Array.from(lines).filter((line) =>
|
||||
line.textContent.includes("LOT001")
|
||||
);
|
||||
if (lineLOT001.length) {
|
||||
console.error("LOT001 shoudld not be displayed as unavailable.");
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
content: "Cancel the move line creation",
|
||||
trigger: ".modal-header:has(.modal-title:contains(Add line: Product Lot)) .btn-close",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Remove the newly created line",
|
||||
trigger:
|
||||
".modal-content .o_data_row:has(.o_field_pick_from input:value(WH/Stock - LOT001)):has(.o_field_float[name=quantity] input:value(0.00)) .o_list_record_remove",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "check that the move contains two lines",
|
||||
trigger:
|
||||
".modal-content:has(.modal-header .modal-title:contains(Detailed Operations)):not(:has(.o_data_row:nth-child(3)))",
|
||||
},
|
||||
{
|
||||
content: "Check that the first line is associated with LOT001",
|
||||
trigger:
|
||||
".modal-content .o_data_row:nth-child(1):has(.o_field_pick_from:contains(WH/Stock - LOT001))",
|
||||
},
|
||||
{
|
||||
content: "Check that the second line is associated with LOT002",
|
||||
trigger:
|
||||
".modal-content .o_data_row:nth-child(2):has(.o_field_pick_from:contains(WH/Stock - LOT002))",
|
||||
},
|
||||
{
|
||||
content: "Modify the quant associated to the second line to fully use LOT003",
|
||||
trigger: ".modal-content .o_data_row:nth-child(2) .o_field_pick_from",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".modal-content .o_data_row:nth-child(2) .o_field_pick_from input",
|
||||
run: "edit LOT003",
|
||||
},
|
||||
{
|
||||
trigger: ".dropdown-item:contains(LOT003)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Modify the quantity of the first line from 10 to 8",
|
||||
trigger: ".modal-content .o_data_row:nth-child(1) .o_data_cell[name=quantity]",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".modal-content .o_data_row:nth-child(1) .o_field_widget[name=quantity] input",
|
||||
run: "edit 8",
|
||||
},
|
||||
{
|
||||
content: "Click on the header to update the total amount",
|
||||
trigger: ".modal-header .modal-title:contains(Detailed Operations)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".modal-content .o_list_number:contains(18.00)",
|
||||
},
|
||||
{
|
||||
trigger: ".modal-content .o_field_x2many_list_row_add > a",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "LOT003 should not appear as it is not available",
|
||||
trigger: ".modal-header .modal-title:contains(Add line: Product Lot)",
|
||||
run: () => {
|
||||
const lines = document.querySelectorAll(".o_data_row .o_data_cell[name=lot_id]");
|
||||
if (lines.length !== 2) {
|
||||
console.error(
|
||||
"Wrong number of available quants: " + lines.length + " instead of 2."
|
||||
);
|
||||
}
|
||||
const lineLOT003 = Array.from(lines).filter((line) =>
|
||||
line.textContent.includes("LOT003")
|
||||
);
|
||||
if (lineLOT003.length) {
|
||||
console.error("LOT003 shoudld not be displayed as unavailable.");
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
content: "Pick LOT001 to create a move line with a quantity of 2.00",
|
||||
trigger: ".o_data_row .o_data_cell[name=lot_id]:contains(LOT001)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".modal-content .o_list_number:contains(20.00)",
|
||||
},
|
||||
{
|
||||
content: "Check that 2 units of LOT001 were added",
|
||||
trigger:
|
||||
".o_data_row:has(.o_field_pick_from input:value(WH/Stock - LOT001)) .o_field_widget[name=quantity] input:value(2.00)",
|
||||
},
|
||||
{
|
||||
content: "Check that the third line is associated with LOT003",
|
||||
trigger:
|
||||
".modal-content .o_data_row:nth-child(3) .o_field_pick_from:contains(WH/Stock - LOT003)",
|
||||
},
|
||||
{
|
||||
content: "Modify the quant associated to the third line to use LOT002",
|
||||
trigger: ".modal-content .o_data_row:nth-child(3) .o_field_pick_from",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".modal-content .o_data_row:nth-child(3) .o_field_pick_from input",
|
||||
run: "edit LOT002",
|
||||
},
|
||||
{
|
||||
trigger: ".dropdown-item:contains(LOT002)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".modal-header .modal-title:contains(Detailed Operations)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".modal-content .o_data_row:nth-child(3) .o_field_pick_from:contains(LOT002)",
|
||||
},
|
||||
{
|
||||
content: "Modify the quantity of the first line from 10 to 15 to change the demand",
|
||||
trigger: ".modal-content .o_data_row:nth-child(3) .o_data_cell[name=quantity]",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".modal-content .o_data_row:nth-child(3) .o_field_widget[name=quantity] input",
|
||||
run: "edit 15",
|
||||
},
|
||||
{
|
||||
content: "Remove the LOT001 line with a quantity of 8.00",
|
||||
trigger:
|
||||
".o_data_row:has(.o_data_cell[name=quantity]:contains(8.00)) .o_list_record_remove",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".modal-content .o_list_number:contains(17.00)",
|
||||
},
|
||||
{
|
||||
trigger: ".modal-content .o_field_x2many_list_row_add > a",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "LOT002 should not appear as it is not available",
|
||||
trigger: ".modal-header .modal-title:contains(Add line: Product Lot)",
|
||||
run: () => {
|
||||
const lines = document.querySelectorAll(".o_data_row .o_data_cell[name=lot_id]");
|
||||
if (lines.length !== 2) {
|
||||
console.error(
|
||||
"Wrong number of available quants: " + lines.length + " instead of 2."
|
||||
);
|
||||
}
|
||||
const lineLOT002 = Array.from(lines).filter((line) =>
|
||||
line.textContent.includes("LOT002")
|
||||
);
|
||||
if (lineLOT002.length) {
|
||||
console.error("LOT002 shoudld not be displayed as unavailable.");
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
content: "Pick LOT001 to create move line to fullfill the demand of 3",
|
||||
trigger: ".o_data_row .o_data_cell[name=lot_id]:contains(LOT001)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".modal-content .o_list_number:contains(20.00)",
|
||||
},
|
||||
{
|
||||
content: "Check that 3 units of LOT001 were added",
|
||||
trigger:
|
||||
".modal-content .o_data_row:has(.o_field_pick_from input:value(WH/Stock - LOT001)):has(.o_field_float[name=quantity] input:value(3.00))",
|
||||
},
|
||||
{
|
||||
trigger: ".modal-content .o_form_button_save",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_list_view.o_field_x2many .o_data_row button[name='action_show_details']",
|
||||
run: "click",
|
||||
},
|
||||
],
|
||||
});
|
||||
137
frontend/stock/static/tests/tours/stock_report_tests.js
Normal file
137
frontend/stock/static/tests/tours/stock_report_tests.js
Normal file
@@ -0,0 +1,137 @@
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("web_tour.tours").add("test_stock_route_diagram_report", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: ".o_breadcrumb",
|
||||
},
|
||||
{
|
||||
trigger: '.o_kanban_record',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: '.nav-item > a:contains("Inventory")',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: '.btn[id="stock.view_diagram_button"]',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ':iframe .o_report_stock_rule',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_context_from_warehouse_filter", {
|
||||
steps: () => [
|
||||
// Add "foo" to the warehouse context key
|
||||
{
|
||||
trigger: ".o_searchview_input",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_searchview_input",
|
||||
run: "edit foo",
|
||||
},
|
||||
{
|
||||
trigger: ".o-dropdown-item:contains(Warehouse):contains(foo)",
|
||||
run: "click",
|
||||
},
|
||||
// Add warehouse A's id to the warehouse context key
|
||||
{
|
||||
trigger: ".o_searchview_input",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_searchview_input",
|
||||
run: "edit warehouse",
|
||||
},
|
||||
{
|
||||
trigger: ".o-dropdown-item:contains(Search Warehouse for:) a.o_expand > i",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o-dropdown-item.o_indent:contains(Warehouse A) a",
|
||||
run: "click",
|
||||
},
|
||||
// Add warehouse B's id to the warehouse context key
|
||||
{
|
||||
trigger: ".o_searchview_input",
|
||||
run: "edit warehouse",
|
||||
},
|
||||
{
|
||||
trigger: ".o-dropdown-item:contains(Search Warehouse for:) a.o_expand > i",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o-dropdown-item.o_indent:contains(Warehouse B) a",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Go to product page",
|
||||
trigger: ".o_kanban_record:has(span:contains(Lovely Product))",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_form_view",
|
||||
run: () => {
|
||||
if (!document.querySelector("button[name=action_product_tmpl_forecast_report]")) {
|
||||
const panelButtons = document.querySelectorAll(
|
||||
".o_control_panel_actions button"
|
||||
);
|
||||
const moreButton = Array.from(panelButtons).find(
|
||||
(button) => button.textContent.trim() == "More"
|
||||
);
|
||||
moreButton.click();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: "button[name=action_product_tmpl_forecast_report]",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_graph_view",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("test_forecast_replenishment", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: ".o_kanban_record:contains(Lovely product)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "button[name=action_product_tmpl_forecast_report]",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "button.o_forecasted_replenish_btn",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".modal-dialog .btn-close",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_web_client:not(:has(.modal-dialog))",
|
||||
},
|
||||
{
|
||||
trigger: "button.o_forecasted_replenish_btn",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "button[name=launch_replenishment]",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_web_client:not(:has(.modal-dialog))",
|
||||
},
|
||||
{
|
||||
trigger:
|
||||
".o_notification:contains(The following replenishment order have been generated)",
|
||||
},
|
||||
],
|
||||
});
|
||||
9
frontend/stock/static/tests/tours/tour_helper.js
Normal file
9
frontend/stock/static/tests/tours/tour_helper.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export function fail(errorMessage) {
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
export function assert(current, expected, info) {
|
||||
if (current !== expected) {
|
||||
fail(`${info}: "${current}" instead of "${expected}".`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user