19.0 vanilla
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.4 KiB |
|
|
@ -1,33 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 70 70">
|
||||
<defs>
|
||||
<mask id="mask" x="0" y="0" width="70" height="70" maskUnits="userSpaceOnUse">
|
||||
<g id="b">
|
||||
<path id="a" d="M4,0H65c4,0,5,1,5,5V65c0,4-1,5-5,5H4c-3,0-4-1-4-5V5C0,1,1,0,4,0Z" fill="#fff" fill-rule="evenodd"/>
|
||||
</g>
|
||||
</mask>
|
||||
<linearGradient id="linear-gradient" x1="-1349.79" y1="477.94" x2="-1350.79" y2="476.94" gradientTransform="matrix(70, 0, 0, -70, 94554.99, 33455.73)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#da956b"/>
|
||||
<stop offset="1" stop-color="#cc7039"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g mask="url(#mask)">
|
||||
<g>
|
||||
<path d="M0,0H70V70H0Z" fill-rule="evenodd" fill="url(#linear-gradient)"/>
|
||||
<path d="M4,1H65c2.67,0,4.33.67,5,2V0H0V3C.67,1.67,2,1,4,1Z" fill="#fff" fill-opacity="0.38" fill-rule="evenodd"/>
|
||||
<path d="M44.25,69H4c-2,0-4-.15-4-4.08V29L13,16h3V43L31.33,27.71,32,28l7-7,10.86,1.83L53,35.66,36.2,52.47l-.1.7L47.93,41.34l4.19,1.57L51,44c2.55.46,2.8,1.59,4.33,3.57a14.92,14.92,0,0,0,3-.14l-1.22,1.21L53.94,59.32Z" fill="#393939" fill-rule="evenodd" opacity="0.32" style="isolation: isolate"/>
|
||||
<path d="M4,69H65c2.67,0,4.33-1,5-3v4H0V66A3.92,3.92,0,0,0,4,69Z" fill-opacity="0.38" fill-rule="evenodd"/>
|
||||
<g>
|
||||
<g opacity="0.4">
|
||||
<path d="M21.4,46a1,1,0,0,0,1.49,0l7.19-8.17,4.86,5.52a1,1,0,0,0,1.35.13.53.53,0,0,0,.13-.13L46,32.53l5,5.13L53,20,37,23l5,5-6.3,7.25-4.87-5.53a1,1,0,0,0-1.48,0L18.92,41.42a1.31,1.31,0,0,0,0,1.68Z"/>
|
||||
<path d="M34.24,54.91a12.79,12.79,0,0,1,.17-2.63H14.08V18.65A1.12,1.12,0,0,0,13,17.46h-1.5a1.12,1.12,0,0,0-1,1.19v35.8a1.12,1.12,0,0,0,1,1.19H34.31C34.28,55.4,34.25,55.16,34.24,54.91Z"/>
|
||||
<path d="M49.15,50.29v4.85a.34.34,0,0,1-.1.25.33.33,0,0,1-.25.1H45.33a.32.32,0,0,1-.25-.1.3.3,0,0,1-.1-.25v-.69a.33.33,0,0,1,.1-.25.36.36,0,0,1,.25-.1h2.43V50.29a.34.34,0,0,1,.35-.35h.69a.33.33,0,0,1,.25.1A.34.34,0,0,1,49.15,50.29Zm4.51,3.81a5.86,5.86,0,0,0-.79-3A6,6,0,0,0,50.72,49a5.94,5.94,0,0,0-5.92,0,5.91,5.91,0,0,0-2.15,2.15,5.94,5.94,0,0,0,0,5.92,5.84,5.84,0,0,0,2.15,2.15,5.94,5.94,0,0,0,5.92,0,5.91,5.91,0,0,0,2.15-2.15A5.83,5.83,0,0,0,53.66,54.1Zm.41-5.44,1.08-.82a.42.42,0,0,1,.57.08l.73,1a.4.4,0,0,1-.08.57l-1.17.88A8.35,8.35,0,0,1,55,58.28a8.26,8.26,0,0,1-7.21,4.15,8.17,8.17,0,0,1-4.18-1.11,8.26,8.26,0,0,1-3-3,8.36,8.36,0,0,1,0-8.36,8.31,8.31,0,0,1,3-3,8.11,8.11,0,0,1,3.16-1.06V45h-.81a.41.41,0,0,1-.41-.41v-.81a.41.41,0,0,1,.41-.4H50a.4.4,0,0,1,.41.4v.81A.41.41,0,0,1,50,45h-.81v.93a8.19,8.19,0,0,1,2.76,1A8.31,8.31,0,0,1,54.07,48.66Z" fill-rule="evenodd"/>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M23.4,44a1,1,0,0,0,1.49,0l7.19-8.17,4.86,5.52a1,1,0,0,0,1.35.13.53.53,0,0,0,.13-.13L48,30.53l5,5.13L55,18,39,21l5,5-6.3,7.25-4.87-5.53a1,1,0,0,0-1.48,0L20.92,39.42a1.31,1.31,0,0,0,0,1.68Z" fill="#fff"/>
|
||||
<path d="M36.24,52.91a12.79,12.79,0,0,1,.17-2.63H16.08V16.65a1.12,1.12,0,0,0-1-1.19h-1.5a1.12,1.12,0,0,0-1,1.19v35.8a1.12,1.12,0,0,0,1,1.19H36.31C36.28,53.4,36.25,53.16,36.24,52.91Z" fill="#fff"/>
|
||||
<path d="M51.15,48.29v4.85a.34.34,0,0,1-.1.25.33.33,0,0,1-.25.1H47.33a.32.32,0,0,1-.25-.1.3.3,0,0,1-.1-.25v-.69a.33.33,0,0,1,.1-.25.36.36,0,0,1,.25-.1h2.43V48.29a.34.34,0,0,1,.35-.35h.69a.33.33,0,0,1,.25.1A.34.34,0,0,1,51.15,48.29Zm4.51,3.81a5.86,5.86,0,0,0-.79-3A6,6,0,0,0,52.72,47a5.94,5.94,0,0,0-5.92,0,5.91,5.91,0,0,0-2.15,2.15,5.94,5.94,0,0,0,0,5.92,5.84,5.84,0,0,0,2.15,2.15,5.94,5.94,0,0,0,5.92,0,5.91,5.91,0,0,0,2.15-2.15A5.83,5.83,0,0,0,55.66,52.1Zm.41-5.44,1.08-.82a.42.42,0,0,1,.57.08l.73,1a.4.4,0,0,1-.08.57l-1.17.88A8.35,8.35,0,0,1,57,56.28a8.26,8.26,0,0,1-7.21,4.15,8.17,8.17,0,0,1-4.18-1.11,8.26,8.26,0,0,1-3-3,8.36,8.36,0,0,1,0-8.36,8.31,8.31,0,0,1,3-3,8.11,8.11,0,0,1,3.16-1.06V43h-.81a.41.41,0,0,1-.41-.41v-.81a.41.41,0,0,1,.41-.4H52a.4.4,0,0,1,.41.4v.81A.41.41,0,0,1,52,43h-.81v.93a8.19,8.19,0,0,1,2.76,1A8.31,8.31,0,0,1,56.07,46.66Z" fill="#fff" fill-rule="evenodd"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<svg width="50" height="50" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg"><path d="M45.445 23.222A22.222 22.222 0 1 0 12.11 42.467l11.111-19.245h22.223Z" fill="#FBB945"/><path d="M5.313 32.53A22.222 22.222 0 1 0 37.889 7.533L26.778 26.778 5.313 32.53Z" fill="#FC868B"/><path d="M23.221 45.444c12.274 0 22.223-9.95 22.223-22.223 0-5.23-1.807-10.039-4.832-13.835a22.128 22.128 0 0 0-13.835-4.831c-12.273 0-22.222 9.949-22.222 22.222 0 5.23 1.807 10.04 4.831 13.835a22.128 22.128 0 0 0 13.835 4.832Z" fill="#985184"/><path d="M7.719 7.303c.646-.63 1.33-1.22 2.05-1.768.227.056.445.161.639.316L26.58 18.796c1.82 1.456 1.946 4.192.297 5.84-1.649 1.647-4.387 1.521-5.845-.297L8.075 8.181a1.651 1.651 0 0 1-.356-.878Z" fill="#fff"/></svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 4 KiB After Width: | Height: | Size: 741 B |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
|
@ -1,28 +1,30 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { Component } from "@odoo/owl";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { Many2OneField } from "@web/views/fields/many2one/many2one_field";
|
||||
import { X2ManyField } from "@web/views/fields/x2many/x2many_field";
|
||||
import { computeM2OProps, Many2One } from "@web/views/fields/many2one/many2one";
|
||||
import { buildM2OFieldDescription, Many2OneField } from "@web/views/fields/many2one/many2one_field";
|
||||
|
||||
export class SoLineField extends Many2OneField {
|
||||
setup() {
|
||||
super.setup();
|
||||
export class SoLineField extends Component {
|
||||
static template = "sale_timesheet.SoLineField";
|
||||
static components = { Many2One };
|
||||
static props = { ...Many2OneField.props };
|
||||
|
||||
const update = this.update;
|
||||
this.update = (value, params = {}) => {
|
||||
update(value, params);
|
||||
if ( // field is unset AND the old & new so_lines are different
|
||||
!this.props.record.data.is_so_line_edited &&
|
||||
this.props.value[0] != (value[0] && value[0].id)
|
||||
) {
|
||||
this.props.record.update({ is_so_line_edited: true });
|
||||
}
|
||||
get m2oProps() {
|
||||
return {
|
||||
...computeM2OProps(this.props),
|
||||
update: (value) => {
|
||||
this.props.record.update({ [this.props.name]: value });
|
||||
if (
|
||||
// field is unset AND the old & new so_lines are different
|
||||
!this.props.record.data.is_so_line_edited &&
|
||||
this.props.record.data[this.props.name].id != value.id
|
||||
) {
|
||||
this.props.record.update({ is_so_line_edited: true });
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class TimesheetsOne2ManyField extends X2ManyField {}
|
||||
TimesheetsOne2ManyField.additionalClasses = ['o_field_one2many'];
|
||||
registry.category("fields").add('so_line_one2many', TimesheetsOne2ManyField); // TODO: Remove me when the gantt view is converted in OWL
|
||||
|
||||
registry.category("fields").add("so_line_field", SoLineField);
|
||||
registry.category("fields").add("so_line_field", {
|
||||
...buildM2OFieldDescription(SoLineField),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates>
|
||||
|
||||
<t t-name="sale_timesheet.SoLineField">
|
||||
<Many2One t-props="m2oProps"/>
|
||||
<span class="text-muted" t-out="this.props.record.fields[this.props.name].falsy_value_label" t-if="props.readonly and !props.record.data.so_line"/>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
odoo.define('sale_timesheet.so_line_many2one', function (require) {
|
||||
"use strict";
|
||||
|
||||
const fieldRegistry = require('web.field_registry');
|
||||
const { FieldOne2Many, FieldMany2One } = require('web.relational_fields');
|
||||
|
||||
const SoLineOne2Many = FieldOne2Many.extend({
|
||||
_onFieldChanged: function (ev) {
|
||||
if (
|
||||
ev.data.changes &&
|
||||
ev.data.changes.hasOwnProperty('timesheet_ids') &&
|
||||
ev.data.changes.timesheet_ids.operation === 'UPDATE' &&
|
||||
ev.data.changes.timesheet_ids.data &&
|
||||
ev.data.changes.timesheet_ids.data.hasOwnProperty('so_line')) {
|
||||
const line = this.value.data.find(line => {
|
||||
return line.id === ev.data.changes.timesheet_ids.id;
|
||||
});
|
||||
if (!line.is_so_line_edited) {
|
||||
ev.data.changes.timesheet_ids.data.is_so_line_edited = true;
|
||||
}
|
||||
}
|
||||
this._super.apply(this, arguments);
|
||||
}
|
||||
});
|
||||
|
||||
const SoLineMany2one = FieldMany2One.extend({
|
||||
/**
|
||||
* @override
|
||||
*
|
||||
* When the user manually changes the field, we need to change the is_so_line_edited field in this model
|
||||
* to know the changes is manual and not via a compute method.
|
||||
*/
|
||||
_onFieldChanged(ev) {
|
||||
if (ev.data.changes && ev.data.changes.hasOwnProperty('so_line') && !ev.data.changes.so_line.is_so_line_edited) {
|
||||
ev.data.changes.is_so_line_edited = true;
|
||||
}
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
fieldRegistry.add('so_line_one2many', SoLineOne2Many);
|
||||
fieldRegistry.add('so_line_field', SoLineMany2one);
|
||||
|
||||
return SoLineOne2Many;
|
||||
|
||||
});
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<button t-name="SaleProjectKanbanView.buttons" type="button" class="btn btn-secondary o_create_sale_order">
|
||||
Create Sales Order
|
||||
</button>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import { mailModels } from "@mail/../tests/mail_test_helpers";
|
||||
import { ProductProduct, SaleOrder, SaleOrderLine } from "@sale_project/../tests/project_task_model";
|
||||
import { defineModels, fields, models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class HrEmployee extends models.Model {
|
||||
_name = "hr.employee";
|
||||
|
||||
name = fields.Char();
|
||||
|
||||
_records = [{ id: 1, name: "Employee 1" }];
|
||||
}
|
||||
|
||||
export class ProjectSaleLineEmployeeMap extends models.Model {
|
||||
_name = "project.sale.line.employee.map";
|
||||
|
||||
project_id = fields.Many2one({ relation: "project.project" });
|
||||
sale_line_id = fields.Many2one({ string: "Sales Order Item", relation: "sale.order.line" });
|
||||
employee_id = fields.Many2one({ string: "Employee", relation: "hr.employee" });
|
||||
price_unit = fields.Float({ string: "Unit Price" });
|
||||
|
||||
_records = [{ id: 1, project_id: 1, employee_id: 1, sale_line_id: 1, price_unit: 200.0 }];
|
||||
}
|
||||
|
||||
export class ProjectProject extends models.Model {
|
||||
_name = "project.project";
|
||||
|
||||
name = fields.Char();
|
||||
sale_line_employee_ids = fields.One2many({ relation: "project.sale.line.employee.map" });
|
||||
|
||||
_records = [{ id: 1, name: "Billable Project", sale_line_employee_ids: [1] }];
|
||||
}
|
||||
|
||||
export const saleTimesheetModels = {
|
||||
ProjectProject,
|
||||
SaleOrderLine,
|
||||
ProjectSaleLineEmployeeMap,
|
||||
HrEmployee,
|
||||
SaleOrder,
|
||||
ProductProduct,
|
||||
};
|
||||
|
||||
export function defineSaleTimesheetModels() {
|
||||
return defineModels({ ...mailModels, ...saleTimesheetModels });
|
||||
}
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
import { describe, expect, test } from "@odoo/hoot";
|
||||
import { click, edit, queryOne } from "@odoo/hoot-dom";
|
||||
import { animationFrame } from "@odoo/hoot-mock";
|
||||
|
||||
import { focus, mailModels } from "@mail/../tests/mail_test_helpers";
|
||||
import { contains, mountView, onRpc } from "@web/../tests/web_test_helpers";
|
||||
import { defineSaleTimesheetModels, saleTimesheetModels } from "./sale_timesheet_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
|
||||
class SaleOrder extends saleTimesheetModels.SaleOrder {
|
||||
_views = {
|
||||
form: `
|
||||
<form>
|
||||
<group>
|
||||
<field name="partner_id" required="True"/>
|
||||
<field name="project_id"/>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Order Lines" name="order_lines">
|
||||
<field name="order_line">
|
||||
<list editable="bottom">
|
||||
<field name="product_id"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</form>
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
||||
class ProjectProject extends saleTimesheetModels.ProjectProject {
|
||||
_views = {
|
||||
form: `
|
||||
<form>
|
||||
<notebook>
|
||||
<page name="billing_employee_rate" string="Invoicing">
|
||||
<field name="sale_line_employee_ids">
|
||||
<list editable="bottom">
|
||||
<field name="employee_id" widget="many2one_avatar_user"/>
|
||||
<field name="sale_line_id" required="True"
|
||||
options="{'no_create': True, 'no_open': True}"
|
||||
context="{
|
||||
'default_partner_id': 1,
|
||||
'default_project_id': 1,
|
||||
}"
|
||||
widget="so_line_create_button"
|
||||
/>
|
||||
<field name="price_unit"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</form>
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
||||
saleTimesheetModels.ProjectProject = ProjectProject;
|
||||
saleTimesheetModels.SaleOrder = SaleOrder;
|
||||
|
||||
defineSaleTimesheetModels();
|
||||
|
||||
onRpc("get_first_service_line", function ({ args, model }) {
|
||||
const [solId] = this.env[model].browse(args[0])[0].order_line;
|
||||
const productId = this.env["sale.order.line"].browse(solId)[0].product_id;
|
||||
const productType = this.env["product.product"].browse(productId)[0].type;
|
||||
if (productType === "service") {
|
||||
expect.step("valid_so");
|
||||
return [solId];
|
||||
} else {
|
||||
expect.step("invalid_so");
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
test("test so_line_create_button widget: valid SO", async () => {
|
||||
const partner_name = mailModels.ResPartner._records.find((partner) => partner.id === 1).name;
|
||||
const project_name = ProjectProject._records.find((project) => project.id === 1).name;
|
||||
await mountView({
|
||||
resId: 1,
|
||||
resModel: "project.project",
|
||||
type: "form",
|
||||
});
|
||||
|
||||
await contains(".o_field_x2many_list_row_add a").click();
|
||||
await focus("[name='sale_line_id'] input");
|
||||
const create_so_button = queryOne("a[aria-label='Create Sales Order']");
|
||||
expect(create_so_button).toBeVisible({
|
||||
message: "The so_line_create_button widget should appear when creating a new record.",
|
||||
});
|
||||
await create_so_button.click();
|
||||
await animationFrame();
|
||||
|
||||
expect("div[name='partner_id'] input").toHaveValue(partner_name, {
|
||||
message:
|
||||
"The default_partner_id set in the field context should be passed in the SO form view.",
|
||||
});
|
||||
expect("div[name='project_id'] input").toHaveValue(project_name, {
|
||||
message:
|
||||
"The default_project_id set in the field context should be passed in the SO form view.",
|
||||
});
|
||||
|
||||
await contains(".modal-content .o_field_x2many_list_row_add a").click();
|
||||
await contains(".modal-content .o_selected_row td[name='product_id'] input").edit(
|
||||
"Service Product 2"
|
||||
);
|
||||
await contains(".modal-content .ui-sortable .o-autocomplete--input").click();
|
||||
await contains(".dropdown-item:nth-child(1)").click();
|
||||
await contains(".modal-content button[class*='o_form_button_save']").click();
|
||||
|
||||
expect(".o_selected_row td[name='sale_line_id'] input").toHaveValue("Service Product 2", {
|
||||
message: "The sale order line should be created and set in the input field.",
|
||||
});
|
||||
// As the SO contains at least one service product, it should be validated and created.
|
||||
expect.verifySteps(["valid_so"]);
|
||||
});
|
||||
|
||||
test("test so_line_create_button widget: invalid SO", async () => {
|
||||
await mountView({
|
||||
resId: 1,
|
||||
resModel: "project.project",
|
||||
type: "form",
|
||||
});
|
||||
|
||||
await contains(".o_field_x2many_list_row_add a").click();
|
||||
await focus("[name='sale_line_id'] input");
|
||||
await contains("a[aria-label='Create Sales Order']").click();
|
||||
await animationFrame();
|
||||
|
||||
await contains(".modal-content .o_field_x2many_list_row_add a").click();
|
||||
await contains(".modal-content .o_selected_row td[name='product_id'] input").edit(
|
||||
"Consumable Product 1"
|
||||
);
|
||||
await contains(".modal-content .ui-sortable .o-autocomplete--input").click();
|
||||
await contains(".dropdown-item:nth-child(1)").click();
|
||||
await contains(".modal-content button[class*='o_form_button_save']").click();
|
||||
|
||||
expect(".o_selected_row td[name='sale_line_id'] input").toHaveValue("", {
|
||||
message: "The sale order line should not be created and set in the input field.",
|
||||
});
|
||||
// As the SO does not contain at least one service product, it should not be validated and created.
|
||||
expect.verifySteps(["invalid_so"]);
|
||||
});
|
||||
|
||||
test("test so_line_create_button widget: visibility conditions", async () => {
|
||||
await mountView({
|
||||
resId: 1,
|
||||
resModel: "project.project",
|
||||
type: "form",
|
||||
});
|
||||
|
||||
await click(".ui-sortable .o_data_row:nth-child(1) div[name='sale_line_id']");
|
||||
await animationFrame();
|
||||
await focus("[name='sale_line_id'] input");
|
||||
expect("a[aria-label='Create Sales Order']").toHaveCount(0, {
|
||||
message:
|
||||
"The so_line_create_button widget should not appear as there is already a value in sale_line_id field.",
|
||||
});
|
||||
await edit("");
|
||||
await click(".ui-sortable .o_data_row:nth-child(1) div[name='employee_id']");
|
||||
await animationFrame();
|
||||
await focus("[name='sale_line_id'] input");
|
||||
expect("a[aria-label='Create Sales Order']").toBeVisible({
|
||||
message:
|
||||
"The so_line_create_button widget should appear as there is no value in sale_line_id field.",
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
editInput,
|
||||
insertText,
|
||||
openFormView,
|
||||
openListView,
|
||||
registerArchs,
|
||||
start,
|
||||
startServer,
|
||||
mailModels,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { beforeEach, describe, expect, getFixture, test } from "@odoo/hoot";
|
||||
import { asyncStep, onRpc, waitForSteps, defineModels } from "@web/../tests/web_test_helpers";
|
||||
import { analyticModels } from "@analytic/../tests/analytic_test_helpers";
|
||||
import { ProjectTask } from "@project/../tests/mock_server/mock_models/project_task";
|
||||
import { SaleOrderLine } from "@sale/../tests/mock_server/mock_models/sale_order_line";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineModels({
|
||||
...mailModels,
|
||||
...analyticModels,
|
||||
ProjectTask,
|
||||
SaleOrderLine,
|
||||
});
|
||||
registerArchs({
|
||||
"account.analytic.line,false,form": `<form>
|
||||
<field name="so_line" widget="so_line_field"/>
|
||||
</form>`,
|
||||
"account.analytic.line,false,list": `<list editable="bottom">
|
||||
<field name="so_line" widget="so_line_field"/>
|
||||
</list>`,
|
||||
"project.task,false,form": `<form>
|
||||
<field name="timesheet_ids">
|
||||
<list editable="bottom">
|
||||
<field name="so_line" widget="so_line_field"/>
|
||||
<field name="is_so_line_edited" column_invisible="True"/>
|
||||
</list>
|
||||
</field>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
let pyEnv;
|
||||
beforeEach(async () => {
|
||||
pyEnv = await startServer();
|
||||
const so_line = pyEnv["sale.order.line"].create({ name: "Sale Order Line 1" });
|
||||
pyEnv["sale.order.line"].create({ name: "Sale Order Line 2" });
|
||||
pyEnv["account.analytic.line"].create({
|
||||
so_line: so_line,
|
||||
});
|
||||
});
|
||||
|
||||
test("Check whether so_line_field widget works as intended in form view", async () => {
|
||||
const target = getFixture();
|
||||
await start();
|
||||
onRpc("account.analytic.line", "web_save", (args) => {
|
||||
expect(args.args[1].is_so_line_edited).toBe(true);
|
||||
asyncStep("web_save");
|
||||
});
|
||||
await openFormView("account.analytic.line");
|
||||
await editInput(target, ".o_field_widget[name=so_line] input", "Sale Order Line 2");
|
||||
await contains(".ui-autocomplete");
|
||||
await click(target.querySelector(".ui-menu-item"));
|
||||
await click(".o_form_button_save");
|
||||
await waitForSteps(["web_save"]);
|
||||
});
|
||||
|
||||
test("Check whether so_line_field widget works as intended in list view", async () => {
|
||||
const target = getFixture();
|
||||
await start();
|
||||
onRpc("account.analytic.line", "web_save", ({ args }) => {
|
||||
expect(args[1].is_so_line_edited).toBe(true);
|
||||
asyncStep("web_save");
|
||||
});
|
||||
await openListView("account.analytic.line");
|
||||
await click(".o_data_cell");
|
||||
await insertText(".o_field_widget[name=so_line] input", "Sale Order Line 2", { replace: true });
|
||||
await contains(".ui-autocomplete");
|
||||
await click(target.querySelector(".ui-menu-item"));
|
||||
await click(target.querySelector(".o_searchview_input"));
|
||||
await waitForSteps(["web_save"]);
|
||||
});
|
||||
|
||||
test("Check whether so_line_field widget works as intended in sub-tree view of timesheets linked to a task", async () => {
|
||||
const target = getFixture();
|
||||
await start();
|
||||
onRpc("project.task", "web_save", ({ args }) => {
|
||||
expect(args[1].timesheet_ids[0][2].is_so_line_edited).toBe(true);
|
||||
asyncStep("web_save");
|
||||
});
|
||||
await openFormView("project.task");
|
||||
await click(".o_field_x2many_list_row_add a");
|
||||
await insertText(".o_field_widget[name=so_line] input", "Sale Order Line 2", { replace: true });
|
||||
await contains(".ui-autocomplete");
|
||||
await click(target.querySelector(".ui-menu-item"));
|
||||
await click(".o_form_button_save");
|
||||
await waitForSteps(["web_save"]);
|
||||
});
|
||||
|
||||
test("Check placeholder string when no so_line linked", async () => {
|
||||
pyEnv["account.analytic.line"].create({ so_line: false });
|
||||
pyEnv["account.analytic.line"]._fields.so_line["falsy_value_label"] = "Non-billable";
|
||||
|
||||
await start();
|
||||
await openListView("account.analytic.line");
|
||||
expect(".o_field_so_line_field.o_field_empty").toHaveText("Non-billable", {
|
||||
message: "Should display 'Non-billable' as placeholder for empty so_line field.",
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -1,166 +1,225 @@
|
|||
odoo.define('sale_timesheet.tour', function (require) {
|
||||
"use strict";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { stepUtils } from "@web_tour/tour_utils";
|
||||
import tourUtils from "@sale/js/tours/tour_utils";
|
||||
|
||||
const {Markup} = require('web.utils');
|
||||
const tour = require('web_tour.tour');
|
||||
import { markup } from "@odoo/owl";
|
||||
|
||||
tour.register('sale_timesheet_tour', {
|
||||
test: true,
|
||||
url: '/web',
|
||||
}, [...tour.stepUtils.goToAppSteps("sale.sale_menu_root", 'Go to the Sales App'),
|
||||
registry.category("web_tour.tours").add('sale_timesheet_tour', {
|
||||
url: '/odoo',
|
||||
steps: () => [
|
||||
...stepUtils.goToAppSteps("sale.sale_menu_root", "Go to the Sales App"),
|
||||
...tourUtils.createNewSalesOrder(),
|
||||
...tourUtils.selectCustomer("Brandon Freeman"),
|
||||
...tourUtils.addProduct("Service Product (Prepaid Hours)"),
|
||||
{
|
||||
trigger: 'button.o_list_button_add',
|
||||
content: 'Click on CREATE button to create a quotation with service products.',
|
||||
}, {
|
||||
trigger: 'div[name="partner_id"] input',
|
||||
content: 'Add the customer for this quotation (e.g. Brandon Freeman)',
|
||||
run: 'text Brandon Freeman',
|
||||
}, {
|
||||
trigger: 'div[name="partner_id"] ul > li:first-child > a:contains(Freeman)',
|
||||
content: 'Select the first item on the autocomplete dropdown',
|
||||
},
|
||||
{
|
||||
trigger: 'td.o_field_x2many_list_row_add > a:first-child',
|
||||
content: 'Click on "Add a product" to add a new product. We will add a service product.',
|
||||
}, {
|
||||
trigger: '.o_field_html[name="product_id"], .o_field_widget[name="product_template_id"] input',
|
||||
content: Markup('Select a prepaid service product <i>(e.g. Service Product (Prepaid Hours))</i>'),
|
||||
run: 'text Service Product (Prepaid Hours)',
|
||||
}, {
|
||||
trigger: 'ul.ui-autocomplete a:contains(Service Product (Prepaid Hours))',
|
||||
content: 'Select the prepaid service product in the autocomplete dropdown',
|
||||
}, {
|
||||
trigger: 'div[name="product_uom_qty"] input',
|
||||
content: "Add 10 hours as ordered quantity for this product.",
|
||||
run: 'text 10',
|
||||
run: "edit 10 && press Tab",
|
||||
}, {
|
||||
trigger: 'div[name="name"] textarea:propValueContains(Service Product)',
|
||||
run: () => {}
|
||||
trigger: '.o_field_cell[name=price_subtotal]:contains(2,500.00)',
|
||||
}, {
|
||||
trigger: 'button[name="action_confirm"]',
|
||||
trigger: "button[name=action_confirm]:enabled",
|
||||
content: 'Click on Confirm button to create a sale order with this quotation.',
|
||||
}, tour.stepUtils.toggleHomeMenu(),
|
||||
...tour.stepUtils.goToAppSteps("project.menu_main_pm", 'Go to the Project app.'),
|
||||
run: "click",
|
||||
}, {
|
||||
content: 'Wait for the confirmation to finish. State should be "Sales Order"',
|
||||
trigger: '.o_field_widget[name=state] .o_arrow_button_current:contains("Sales Order")',
|
||||
},
|
||||
...stepUtils.toggleHomeMenu(),
|
||||
...stepUtils.goToAppSteps("project.menu_main_pm", 'Go to the Project app.'),
|
||||
{
|
||||
trigger: 'button.o-kanban-button-new',
|
||||
content: 'Add a new project.',
|
||||
run: "click",
|
||||
}, {
|
||||
isActive: ['button.o-kanban-button-new.dropdown'], // if the project template dropdown is active
|
||||
trigger: 'button.o-dropdown-item:contains("New Project")',
|
||||
content: 'Let\'s create a regular project.',
|
||||
tooltipPosition: 'right',
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: '.o_field_widget.o_project_name input',
|
||||
content: 'Select your project name (e.g. Project for Freeman)',
|
||||
run: 'text Project for Freeman',
|
||||
run: "edit Project for Freeman",
|
||||
}, {
|
||||
trigger: 'div[name="allow_billable"] input',
|
||||
run: 'click',
|
||||
}, {
|
||||
trigger: 'button[name="action_view_tasks"]',
|
||||
content: 'Click on Create button to create and enter to this newest project.',
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: ".breadcrumb-item.o_back_button",
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: ".o_kanban_record:contains('Project for Freeman')",
|
||||
}, {
|
||||
trigger: ".o_switch_view.o_list",
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: "tr.o_data_row td[name='name']:contains('Project for Freeman')",
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: ".nav-link:contains('Settings')",
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: "div[name='allow_milestones'] input",
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: "button[name='action_view_tasks']",
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: 'div.o_kanban_header > div:first-child input',
|
||||
content: 'Select a name of your kanban column (e.g. To Do)',
|
||||
run: 'text To Do',
|
||||
run: "edit To Do",
|
||||
}, {
|
||||
trigger: 'button.o_kanban_add',
|
||||
content: 'Click on Add button to create the column.',
|
||||
}, {
|
||||
run: "click",
|
||||
},{
|
||||
content: "wait the new column is created",
|
||||
trigger: ".o_kanban_renderer .o_kanban_group .o_kanban_header_title:contains(to do)",
|
||||
},{
|
||||
trigger: 'button.o-kanban-button-new',
|
||||
content: 'Click on Create button to create a task into your project.',
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: 'div[name="name"] > input',
|
||||
trigger: 'div[name="display_name"] > input',
|
||||
content: 'Select the name of the task (e.g. Onboarding)',
|
||||
run: 'text Onboarding',
|
||||
run: "edit Onboarding",
|
||||
}, {
|
||||
trigger: 'button.o_kanban_edit',
|
||||
content: 'Click on Edit button to enter to the form view of the task.',
|
||||
position: 'bottom',
|
||||
tooltipPosition: 'bottom',
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: 'div[name="partner_id"] input',
|
||||
content: Markup('Select the customer of your Sales Order <i>(e.g. Brandon Freeman)</i>. Since we have a Sales Order for this customer with a prepaid service product which the remaining hours to deliver is greater than 0, the Sales Order Item in the task should be contain the Sales Order Item containing this prepaid service product.'),
|
||||
run: 'text Brandon Freeman',
|
||||
content: markup('Select the customer of your Sales Order <i>(e.g. Brandon Freeman)</i>. Since we have a Sales Order for this customer with a prepaid service product which the remaining hours to deliver is greater than 0, the Sales Order Item in the task should be contain the Sales Order Item containing this prepaid service product.'),
|
||||
run: "edit Brandon Freeman",
|
||||
}, {
|
||||
trigger: 'div[name="partner_id"] ul > li:first-child > a:contains(Freeman)',
|
||||
content: 'Select the customer in the autocomplete dropdown.',
|
||||
}, {
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "div.o_notebook_headers",
|
||||
},
|
||||
{
|
||||
trigger: 'a.nav-link:contains(Timesheets)',
|
||||
extra_trigger: 'div.o_notebook_headers',
|
||||
content: 'Click on Timesheets page to log a timesheet',
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: 'div[name="timesheet_ids"] td.o_field_x2many_list_row_add a[role="button"]',
|
||||
content: 'Click on Add a line to create a new timesheet into the task.',
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: '.o_field_x2many div[name="name"] input',
|
||||
content: 'Enter a description for this timesheet',
|
||||
run: "edit work",
|
||||
}, {
|
||||
trigger: 'div[name="unit_amount"] input',
|
||||
content: 'Enter one hour for this timesheet',
|
||||
run: 'text 1',
|
||||
run: "edit 1",
|
||||
}, {
|
||||
trigger: 'i.o_optional_columns_dropdown_toggle',
|
||||
content: 'The so_line field should be hidden by default. We check if it is the case by adding this field in the timesheet list view',
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: 'input[name="so_line"]',
|
||||
content: 'Check the so_line field to display the column on the list view.',
|
||||
run: function (actions) {
|
||||
if (!this.$anchor.prop('checked')) {
|
||||
actions.click(this.$anchor);
|
||||
if (!this.anchor.checked) {
|
||||
actions.click();
|
||||
}
|
||||
},
|
||||
}, {
|
||||
trigger: 'button.o_form_button_save i',
|
||||
content: 'Manually save the records (sale order should be filled based on the partner picked for this task',
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: 'button[name="action_view_so"]',
|
||||
content: 'Click on this stat button to see the SO linked to the SOL of the task.',
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: 'div[name="order_line"]',
|
||||
content: 'Check if the quantity delivered is equal to 1 hour.',
|
||||
run: function () {
|
||||
const $header = this.$anchor.find('thead > tr');
|
||||
if (!$header || $header.length === 0)
|
||||
run({ queryFirst }) {
|
||||
const header = this.anchor.querySelectorAll("thead > tr");
|
||||
if (!header || header.length === 0)
|
||||
console.error('No Sales Order Item is found in the Sales Order.');
|
||||
const tr = $header[0];
|
||||
const tr = [...header][0];
|
||||
let index = -1;
|
||||
for (let i = 0; i < tr.children.length; i++) {
|
||||
const th = tr.children.item(i);
|
||||
if (th.dataset && th.dataset.name === 'qty_delivered')
|
||||
index = i;
|
||||
}
|
||||
const qtyDelivered = this.$anchor.find(`tbody > tr:first-child > td.o_data_cell:eq(${index})`).text();
|
||||
if (qtyDelivered !== "1.00")
|
||||
const qtyDelivered = queryFirst(`tbody > tr:first-child > td.o_data_cell:eq(${index})`, { root: this.anchor });
|
||||
if (qtyDelivered.textContent !== "1.00")
|
||||
console.error('The quantity delivered on this Sales Order Item should be equal to 1.00 hour. qtyDelivered = ' + qtyDelivered);
|
||||
},
|
||||
}, {
|
||||
trigger: 'button[data-menu-xmlid="project.menu_project_config"]',
|
||||
content: 'Click on the Configuration menu.',
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: '.dropdown-item[data-menu-xmlid="project.menu_projects_config"]',
|
||||
content: 'Select Configuration > Projects.',
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: 'button.o_list_button_add',
|
||||
content: 'Click on Create button to create a new project and see the different configuration available for the project.',
|
||||
run: "click",
|
||||
}, {
|
||||
isActive: ['button.o_list_button_add.dropdown'], // if the project template dropdown is active
|
||||
trigger: 'button.o-dropdown-item:contains("New Project")',
|
||||
content: 'Let\'s create a regular project.',
|
||||
tooltipPosition: 'right',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "div.o_notebook_headers",
|
||||
},
|
||||
{
|
||||
trigger: 'a.nav-link[name="settings"]',
|
||||
extra_trigger: 'div.o_notebook_headers',
|
||||
content: 'Click on Settings page to check the allow_billable checkbox',
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: 'div[name="allow_billable"] input',
|
||||
content: 'Check the allow_billable',
|
||||
run: function (actions) {
|
||||
if (!this.$anchor.prop('checked')) {
|
||||
actions.click(this.$anchor);
|
||||
if (!this.anchor.checked) {
|
||||
actions.click();
|
||||
}
|
||||
}
|
||||
}, {
|
||||
trigger: 'div[name="partner_id"] input',
|
||||
content: Markup('Add the customer for this project to select an SO and SOL for this customer <i>(e.g. Brandon Freeman)</i>.'),
|
||||
run: 'text Brandon Freeman',
|
||||
content: markup('Add the customer for this project to select an SO and SOL for this customer <i>(e.g. Brandon Freeman)</i>.'),
|
||||
run: "edit Brandon Freeman",
|
||||
}, {
|
||||
trigger: 'div[name="partner_id"] ul > li:first-child > a:contains(Freeman)',
|
||||
content: 'Select the customer in the autocomplete dropdown',
|
||||
}, {
|
||||
trigger: 'a.nav-link[name="billing_employee_rate"]',
|
||||
extra_trigger: 'div.o_notebook_headers',
|
||||
content: 'Click on Invoicing tab to configure the invoicing of this project.',
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: 'div[name="sale_line_id"] input',
|
||||
content: 'Select a Sales Order Item as Default Sales Order Item for each task in this project.',
|
||||
run: 'text S',
|
||||
run: "edit S",
|
||||
}, {
|
||||
trigger: '[name="sale_line_id"] ul.ui-autocomplete > li:first-child > a:not(:has(i.fa))',
|
||||
content: 'Select the Sales Order Item in the autocomplete dropdown.',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "div.o_notebook_headers",
|
||||
},
|
||||
{
|
||||
trigger: 'a.nav-link[name="billing_employee_rate"]',
|
||||
content: 'Click on Invoicing tab to configure the invoicing of this project.',
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: 'div[name="sale_line_employee_ids"] td.o_field_x2many_list_row_add > a[role="button"]',
|
||||
content: 'Click on Add a line on the mapping list view.',
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: 'div[name="sale_line_employee_ids"] div[name="employee_id"] input',
|
||||
content: 'Select an employee to link a Sales Order Item on his timesheets into this project.',
|
||||
|
|
@ -168,137 +227,151 @@ tour.register('sale_timesheet_tour', {
|
|||
}, {
|
||||
trigger: '[name="employee_id"] ul.ui-autocomplete > li:first-child > a:not(:has(i.fa))',
|
||||
content: 'Select the first employee in the autocomplete dropdown',
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: 'div[name="sale_line_employee_ids"] div[name="sale_line_id"] input',
|
||||
content: 'Select the Sales Order Item to link to the timesheets of this employee.',
|
||||
position: 'bottom',
|
||||
run: 'text S',
|
||||
tooltipPosition: 'bottom',
|
||||
run: "edit S",
|
||||
}, {
|
||||
trigger: '[name=sale_line_id] ul.ui-autocomplete > li:first-child > a:not(:has(i.fa))',
|
||||
content: 'Select the first Sales Order Item in the autocomplete dropdown.',
|
||||
run: 'click'
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: 'h1 > div[name="name"] > input',
|
||||
trigger: 'h1 > div[name="name"] > div > textarea',
|
||||
content: 'Set Project name',
|
||||
run: 'text Project with employee mapping',
|
||||
run: "edit Project with employee mapping",
|
||||
}, {
|
||||
trigger: '.dropdown-item[data-menu-xmlid="project.menu_main_pm"]',
|
||||
trigger: '[data-menu-xmlid="project.menu_projects"]',
|
||||
content: 'Select Project main menu',
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: '.oe_kanban_global_click :contains("Project for Freeman") button.o_dropdown_kanban',
|
||||
content: 'Open the project dropdown',
|
||||
trigger: ".o_switch_view.o_list",
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: '.o_kanban_record:contains("Project for Freeman") .dropdown-menu a:contains("Settings")',
|
||||
content: 'Start editing the project',
|
||||
// timer: 300,
|
||||
trigger: "tr.o_data_row td[name='name']:contains('Project for Freeman')",
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: 'div[name="partner_id"] input',
|
||||
content: Markup('Add the customer for this project to select an SO and SOL for this customer <i>(e.g. Brandon Freeman)</i>.'),
|
||||
run: 'text Brandon Freeman',
|
||||
content: markup('Add the customer for this project to select an SO and SOL for this customer <i>(e.g. Brandon Freeman)</i>.'),
|
||||
run: "edit Brandon Freeman",
|
||||
}, {
|
||||
trigger: 'div[name="partner_id"] ul > li:first-child > a:contains(Freeman)',
|
||||
content: 'Select the customer in the autocomplete dropdown',
|
||||
}, {
|
||||
trigger: 'a.nav-link[name="billing_employee_rate"]',
|
||||
extra_trigger: 'div.o_notebook_headers',
|
||||
content: 'Click on Invoicing tab to configure the invoicing of this project.',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: "div.o_notebook_headers",
|
||||
},
|
||||
{
|
||||
trigger: 'a.nav-link[name="settings"]',
|
||||
content: 'Click on Settings tab to configure this project.',
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: 'div[name="sale_line_id"] input',
|
||||
content: 'Select the first sale order of the list',
|
||||
run: 'text Prepaid',
|
||||
run: "edit Prepaid",
|
||||
}, {
|
||||
trigger: 'ul.ui-autocomplete > li:first-child > a:not(:has(i.fa))',
|
||||
content: 'Select the first item on the autocomplete dropdown',
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: '.o_back_button',
|
||||
content: 'Go back to the kanban view the project created',
|
||||
trigger: '[data-menu-xmlid="project.menu_projects"]',
|
||||
content: 'Select Project main menu',
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: '.oe_kanban_global_click :contains("Project for Freeman")',
|
||||
trigger: '.o_kanban_record:contains("Project for Freeman")',
|
||||
content: 'Open the project',
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: ".o_project_updates_breadcrumb",
|
||||
content: 'Open Updates',
|
||||
trigger: ".o_control_panel_navigation button i.fa-sliders",
|
||||
content: 'Open embedded actions',
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: ".o_rightpanel_section[name='sales'] .o_rightpanel_title:contains('Sales')",
|
||||
content: 'Check the user sees Sales section',
|
||||
run: function () {},
|
||||
trigger: "span.o-dropdown-item:contains('Top Menu')",
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: ".o_rightpanel_section[name='sales'] .o_rightpanel_data:contains('Prepaid Hours')",
|
||||
content: 'Check the user sees a line in the Sales section',
|
||||
// timer: 300,
|
||||
run: function () {},
|
||||
trigger: ".o-dropdown-item div span:contains('Dashboard')",
|
||||
content: "Put Dashboard in the embedded actions",
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: ".o_rightpanel_section .oe_button_box .o_stat_text:contains('Sales Orders')",
|
||||
content: 'Check the user sees Sales Orders Stat Button',
|
||||
run: function () {},
|
||||
trigger: ".o_embedded_actions button span:contains('Dashboard')",
|
||||
content: "Open Dashboard",
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: ".o_rightpanel_section[name='profitability'] .o_rightpanel_title:contains('Profitability')",
|
||||
content: 'Check the user sees Profitability section',
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_rightpanel_section[name='profitability'] .o_rightpanel_data > .o_rightpanel_subsection:eq(0) > table > thead > tr > th:eq(0):contains('Revenues')",
|
||||
trigger: ".o_rightpanel_section[name='profitability'] .o_rightpanel_data > div > .o_rightpanel_subsection:eq(0) > table > thead > tr > th:eq(0):contains('Revenues')",
|
||||
content: 'Check the user sees Profitability subsection row',
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_rightpanel_section[name='profitability'] .o_rightpanel_data > .o_rightpanel_subsection:eq(1) > table > thead > tr > th:eq(0):contains('Costs')",
|
||||
content: 'Check the user sees Profitability subsection row',
|
||||
run: function () {},
|
||||
trigger: "button.o_group_caret:has(.fa-caret-right)",
|
||||
content: 'Check that the dropdown button is present',
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: ".o_rightpanel_section[name='profitability'] .o_rightpanel_data > .o_rightpanel_subsection:eq(2) > table > thead > tr > th:eq(0):contains('Margin')",
|
||||
trigger: "th:contains('Sales Order Items')",
|
||||
content: 'Check that the sale items section is present',
|
||||
}, {
|
||||
trigger: "button.o_group_caret:has(.fa-caret-down)",
|
||||
content: 'Check that the button has changed',
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: ".o_rightpanel_section[name='profitability'] .o_rightpanel_data > div > .o_rightpanel_subsection:eq(1) > table > thead > tr > th:eq(0):contains('Costs')",
|
||||
content: 'Check the user sees Profitability subsection row',
|
||||
}, {
|
||||
trigger: ".o_rightpanel_section[name='profitability'] .o_rightpanel_data > div > .o_rightpanel_subsection:eq(2) > table > thead > tr > th:eq(0):contains('Total')",
|
||||
content: 'Check the user sees Profitability subsection row',
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_rightpanel_section[name='milestones'] .o_rightpanel_title:contains('Milestones')",
|
||||
content: 'Check the user sees Milestones section',
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: ".o_add_milestone a",
|
||||
content: "Add a first milestone",
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: ".o_list_button_add",
|
||||
content: "Add a first milestone",
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: "div.o_field_widget[name=name] input",
|
||||
content: "Edit new Milestone",
|
||||
run: 'text New milestone',
|
||||
run: "edit New milestone",
|
||||
}, {
|
||||
trigger: "div[name=deadline] .datetimepicker-input",
|
||||
trigger: "input[data-field=deadline]",
|
||||
content: "Edit new Milestone",
|
||||
run: 'text 12/12/2099',
|
||||
run: "edit 12/12/2099",
|
||||
}, {
|
||||
trigger: ".modal-footer button",
|
||||
trigger: ".o_list_button_save",
|
||||
content: "Save new Milestone",
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: ".breadcrumb-item.o_back_button",
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: ".o-kanban-button-new",
|
||||
content: "Create new Project Update",
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: "div.o_field_widget[name=name] input",
|
||||
content: "Give a name to Project Update",
|
||||
run: 'text New update',
|
||||
}, {
|
||||
trigger: ".o_field_widget[name=description] h3:contains('Sold')",
|
||||
content: "Sold title must be in description in description",
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_field_widget[name=description] td:contains('Prepaid Hours')",
|
||||
content: "Prepaid Hours title must be in description",
|
||||
run: function () {},
|
||||
run: "edit New update",
|
||||
}, {
|
||||
trigger: ".o_field_widget[name=description] h3:contains('Profitability')",
|
||||
content: "Profitability title must be in description",
|
||||
run: function () {},
|
||||
}, {
|
||||
trigger: ".o_field_widget[name=description] h3:contains('Milestones')",
|
||||
content: "Milestones title must be in description",
|
||||
run: function () {},
|
||||
},
|
||||
// Those steps are currently needed in order to prevent the following issue:
|
||||
// "Form views in edition mode are automatically saved when the page is closed, which leads to stray network requests and inconsistencies."
|
||||
{
|
||||
trigger: '.o_back_button',
|
||||
content: 'Go back to the kanban view and the project update will be added on that view',
|
||||
run: "click",
|
||||
}, {
|
||||
trigger: '.o_controller_with_rightpanel',
|
||||
content: 'Check the kanban view of project update is rendered to be sure the user leaves the form view and the project update is created',
|
||||
run: function() {},
|
||||
},
|
||||
tour.stepUtils.toggleHomeMenu(),
|
||||
...tour.stepUtils.goToAppSteps("project.menu_main_pm", 'Go to the Project app.'),
|
||||
]);
|
||||
});
|
||||
...stepUtils.toggleHomeMenu(),
|
||||
...stepUtils.goToAppSteps("project.menu_main_pm", 'Go to the Project app.'),
|
||||
]});
|
||||
|
|
|
|||