mirror of
https://github.com/bringout/oca-ocb-accounting.git
synced 2026-04-22 02:42:08 +02:00
19.0 vanilla
This commit is contained in:
parent
ba20ce7443
commit
768b70e05e
2357 changed files with 1057103 additions and 712486 deletions
|
|
@ -0,0 +1,124 @@
|
|||
import {
|
||||
click,
|
||||
insertText,
|
||||
openFormView,
|
||||
start,
|
||||
startServer,
|
||||
triggerHotkey
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { expect, test } from "@odoo/hoot";
|
||||
import { asyncStep, contains, defineModels, fields, onRpc, models, waitForSteps} from "@web/../tests/web_test_helpers";
|
||||
import { defineAccountModels } from "./account_test_helpers";
|
||||
|
||||
defineAccountModels();
|
||||
|
||||
test("When I switch tabs, it saves", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const accountMove = pyEnv["account.move"].create({ name: "move0" });
|
||||
await start();
|
||||
onRpc("account.move", "web_save", () => {
|
||||
asyncStep("tab saved");
|
||||
});
|
||||
await openFormView("account.move", accountMove, {
|
||||
arch: `<form js_class='account_move_form'>
|
||||
<sheet>
|
||||
<notebook>
|
||||
<page id="invoice_tab" name="invoice_tab" string="Invoice Lines">
|
||||
<field name="name"/>
|
||||
</page>
|
||||
<page id="aml_tab" string="Journal Items" name="aml_tab"></page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>`,
|
||||
});
|
||||
await insertText("[name='name'] input", "somebody save me!");
|
||||
triggerHotkey("Enter");
|
||||
await click('a[name="aml_tab"]');
|
||||
await waitForSteps(["tab saved"]);
|
||||
});
|
||||
|
||||
test("Confirmation dialog on delete contains a warning", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const accountMove = pyEnv["account.move"].create({ name: "move0" });
|
||||
await start();
|
||||
onRpc("account.move", "check_move_sequence_chain", () => {
|
||||
return false;
|
||||
});
|
||||
await openFormView("account.move", accountMove, {
|
||||
arch: `<form js_class='account_move_form'>
|
||||
<sheet>
|
||||
<notebook>
|
||||
<page id="invoice_tab" name="invoice_tab" string="Invoice Lines">
|
||||
<field name="name"/>
|
||||
</page>
|
||||
<page id="aml_tab" string="Journal Items" name="aml_tab"></page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>`,
|
||||
});
|
||||
await contains(".o_cp_action_menus button").click();
|
||||
await contains(".o_menu_item:contains(Delete)").click();
|
||||
expect(".o_dialog div.text-danger").toHaveText("This operation will create a gap in the sequence.", {
|
||||
message: "warning message has been added in the dialog"
|
||||
});
|
||||
});
|
||||
class AccountMove extends models.Model {
|
||||
line_ids = fields.One2many({
|
||||
string: "Invoice Lines",
|
||||
relation: "account.move.line",
|
||||
})
|
||||
|
||||
_records = [{ id: 1, name: "account.move" }]
|
||||
}
|
||||
class AccountMoveLine extends models.Model {
|
||||
name = fields.Char();
|
||||
product_id = fields.Many2one({
|
||||
string:"Product",
|
||||
relation:"product",
|
||||
});
|
||||
move_id = fields.Many2one({
|
||||
string: "Account Move",
|
||||
relation: "account.move",
|
||||
})
|
||||
}
|
||||
class Product extends models.Model {
|
||||
name = fields.Char();
|
||||
_records = [{ id: 1, name: "testProduct" }];
|
||||
}
|
||||
|
||||
defineModels({ Product, AccountMoveLine, AccountMove });
|
||||
|
||||
test("Update description on product line", async() => {
|
||||
const pyEnv = await startServer();
|
||||
const productId = pyEnv["product"].browse([1]);
|
||||
const accountMove = pyEnv["account.move"].browse([1]);
|
||||
pyEnv["account.move.line"].create({ name: productId[0].name, product_id: productId[0].id, move_id: accountMove[0].id });
|
||||
await start();
|
||||
onRpc("account.move", "web_save", () => { asyncStep("save")});
|
||||
await openFormView("account.move", accountMove[0].id, {
|
||||
arch: `<form js_class="account_move_form">
|
||||
<sheet>
|
||||
<notebook>
|
||||
<page id="invoice_tab" name="invoice_tab" string="Invoice Lines">
|
||||
<field name="invoice_line_ids" mode="list" widget="product_label_section_and_note_field_o2m">
|
||||
<list name="journal_items" editable="bottom" string="Journal Items">
|
||||
<field name="product_id" widget="product_label_section_and_note_field" readonly="0"/>
|
||||
<field name="name" widget="section_and_note_text" optional="show"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
await click(".o_many2one");
|
||||
await contains("#labelVisibilityButtonId").click()
|
||||
await insertText("textarea[placeholder='Enter a description']", "testDescription");
|
||||
await click(".o_form_button_save");
|
||||
await waitForSteps(["save"]);
|
||||
|
||||
const line = pyEnv["account.move.line"].browse([1])[0];
|
||||
expect(line.name).toBe("testProduct\ntestDescription");
|
||||
|
||||
});
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
odoo.define('account.reconciliation_field_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
var FormView = require('web.FormView');
|
||||
var testUtils = require('web.test_utils');
|
||||
|
||||
var createView = testUtils.createView;
|
||||
|
||||
QUnit.module('account', {
|
||||
beforeEach: function () {
|
||||
this.data = {
|
||||
'account.move': {
|
||||
fields: {
|
||||
payments_widget: {string: "payments_widget data", type: "char"},
|
||||
outstanding_credits_debits_widget: {string: "outstanding_credits_debits_widget data", type: "char"},
|
||||
},
|
||||
records: [{
|
||||
id: 1,
|
||||
payments_widget: {"content": [{"digits": [69, 2], "currency": "$", "amount": 555.0, "name": "Customer Payment: INV/2017/0004", "date": "2017-04-25", "position": "before", "ref": "BNK1/2017/0003 (INV/2017/0004)", "payment_id": 22, "move_id": 10, "partial_id": 38, "journal_name": "Bank"}], "outstanding": false, "title": "Less Payment"},
|
||||
outstanding_credits_debits_widget: {"content": [{"digits": [69, 2], "currency": "$", "amount": 100.0, "journal_name": "INV/2017/0004", "position": "before", "id": 20}], "move_id": 4, "outstanding": true, "title": "Outstanding credits"},
|
||||
}]
|
||||
},
|
||||
};
|
||||
}
|
||||
}, function () {
|
||||
QUnit.module('Reconciliation');
|
||||
|
||||
QUnit.test('Reconciliation form field [REQUIRES FOCUS]', async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
var form = await createView({
|
||||
View: FormView,
|
||||
model: 'account.move',
|
||||
data: this.data,
|
||||
arch: '<form>'+
|
||||
'<field name="outstanding_credits_debits_widget" widget="payment"/>'+
|
||||
'<field name="payments_widget" widget="payment"/>'+
|
||||
'</form>',
|
||||
res_id: 1,
|
||||
mockRPC: function (route, args) {
|
||||
if (args.method === 'js_remove_outstanding_partial') {
|
||||
assert.deepEqual(args.args, [10, 38], "should call js_remove_outstanding_partial {warning: required focus}");
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (args.method === 'js_assign_outstanding_line') {
|
||||
assert.deepEqual(args.args, [4, 20], "should call js_assign_outstanding_line {warning: required focus}");
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (args.method === 'action_open_business_doc') {
|
||||
assert.deepEqual(args.args, [10], "should call action_open_business_doc {warning: required focus}");
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(form.$('.o_field_widget[name="payments_widget"]').text().replace(/[\s\n\r]+/g, ' '),
|
||||
" Paid on 04/25/2017 $ 555.00 ",
|
||||
"should display payment information");
|
||||
|
||||
await testUtils.dom.click(form.$('.o_field_widget[name="outstanding_credits_debits_widget"] .outstanding_credit_assign'));
|
||||
|
||||
assert.strictEqual(form.$('.o_field_widget[name="outstanding_credits_debits_widget"]').text().replace(/[\s\n\r]+/g, ' '),
|
||||
" Outstanding credits Add INV/2017/0004 $ 100.00 ",
|
||||
"should display outstanding information");
|
||||
|
||||
form.$('.o_field_widget[name="payments_widget"] .js_payment_info').focus();
|
||||
await testUtils.nextTick();
|
||||
await testUtils.dom.click(form.$('.popover .js_open_payment'));
|
||||
|
||||
form.$('.o_field_widget[name="payments_widget"] .js_payment_info').focus();
|
||||
await testUtils.nextTick();
|
||||
await testUtils.dom.click(form.$('.popover .js_unreconcile_payment'));
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import { AccountMove } from "./mock_server/mock_models/account_move";
|
||||
import { AccountMoveLine } from "./mock_server/mock_models/account_move_line";
|
||||
import { mailModels } from "@mail/../tests/mail_test_helpers";
|
||||
import { defineModels } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export const accountModels = {
|
||||
AccountMove,
|
||||
AccountMoveLine,
|
||||
};
|
||||
|
||||
export function defineAccountModels() {
|
||||
return defineModels({ ...mailModels, ...accountModels });
|
||||
}
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
import { describe, expect, test } from "@odoo/hoot";
|
||||
import { setInputFiles } from "@odoo/hoot-dom";
|
||||
import {
|
||||
contains,
|
||||
defineModels,
|
||||
fields,
|
||||
mockService,
|
||||
models,
|
||||
mountView,
|
||||
onRpc,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
import { defineMailModels } from "@mail/../tests/mail_test_helpers";
|
||||
|
||||
class Partner extends models.Model {
|
||||
name = fields.Char();
|
||||
type = fields.Char();
|
||||
|
||||
_records = [
|
||||
{
|
||||
id: 7,
|
||||
name: "first record",
|
||||
type: "purchase",
|
||||
},
|
||||
];
|
||||
_views = {
|
||||
form: `
|
||||
<form>
|
||||
<widget name="account_file_uploader"/>
|
||||
<field name="name" required="1"/>
|
||||
</form>
|
||||
`,
|
||||
list: `
|
||||
<list>
|
||||
<field name="id"/>
|
||||
<field name="name"/>
|
||||
</list>
|
||||
`,
|
||||
search: `<search/>`,
|
||||
};
|
||||
}
|
||||
|
||||
class AccountPaymentTerm extends models.Model {
|
||||
_name = "account_payment_term";
|
||||
|
||||
line_ids = fields.One2many({
|
||||
string: "Payment Term Lines",
|
||||
relation: "account_payment_term_line",
|
||||
});
|
||||
_records = [
|
||||
{
|
||||
id: 1,
|
||||
line_ids: [1, 2],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
class AccountPaymentTermLine extends models.Model {
|
||||
_name = "account_payment_term_line";
|
||||
|
||||
value_amount = fields.Float({ string: "Due" });
|
||||
_records = [
|
||||
{
|
||||
id: 1,
|
||||
value_amount: 0,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
value_amount: 50,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
defineModels([AccountPaymentTerm, AccountPaymentTermLine, Partner]);
|
||||
defineMailModels();
|
||||
|
||||
describe("AccountFileUploader", () => {
|
||||
test("widget contains context based on the record despite field not in view", async () => {
|
||||
onRpc("ir.attachment", "create", () => {
|
||||
expect.step("create ir.attachment");
|
||||
return [99];
|
||||
});
|
||||
|
||||
onRpc("account.journal", "create_document_from_attachment", ({ kwargs }) => {
|
||||
expect.step("create_document_from_attachment");
|
||||
expect(kwargs.context.default_journal_id).toBe(7, {
|
||||
message: "create documents in correct journal",
|
||||
});
|
||||
expect(kwargs.context.default_move_type).toBe("in_invoice", {
|
||||
message: "create documents with correct move type",
|
||||
});
|
||||
return {
|
||||
name: "Generated Documents",
|
||||
domain: [],
|
||||
res_model: "partner",
|
||||
type: "ir.actions.act_window",
|
||||
context: {},
|
||||
views: [
|
||||
[false, "list"],
|
||||
[false, "form"],
|
||||
],
|
||||
view_mode: "list, form",
|
||||
};
|
||||
});
|
||||
mockService("action", {
|
||||
doAction(action) {
|
||||
expect.step("doAction");
|
||||
expect(action.type).toBe("ir.actions.act_window", {
|
||||
message: "do action after documents created",
|
||||
});
|
||||
},
|
||||
});
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
resId: 7,
|
||||
});
|
||||
|
||||
expect(".o_widget_account_file_uploader").toHaveCount(1);
|
||||
const file = new File(["test"], "fake_file.txt", { type: "text/plain" });
|
||||
await contains(".o_widget_account_file_uploader a").click();
|
||||
await setInputFiles([file]);
|
||||
await expect.waitForSteps([
|
||||
"create ir.attachment",
|
||||
"create_document_from_attachment",
|
||||
"doAction",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("AccountMoveUploadKanbanView", () => {
|
||||
test.tags("desktop");
|
||||
test("can render AccountMoveUploadKanbanView", async () => {
|
||||
Partner._views.kanban = `
|
||||
<kanban js_class="account_documents_kanban">
|
||||
<templates>
|
||||
<t t-name="card">
|
||||
<field name="name"/>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
`;
|
||||
onRpc("res.company", "search_read", () => [{ id: 1, country_code: "US" }]);
|
||||
await mountView({
|
||||
type: "kanban",
|
||||
resModel: "partner",
|
||||
});
|
||||
|
||||
expect(".o_control_panel .o_button_upload_bill:visible").toHaveCount(1);
|
||||
expect(".o_kanban_record:not(.o_kanban_ghost)").toHaveCount(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("PaymentTermsLineWidget", () => {
|
||||
test("records don't get abandoned after clicking globally or on an exisiting record", async () => {
|
||||
await mountView({
|
||||
type: "form",
|
||||
resModel: "account_payment_term",
|
||||
resId: 1,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="line_ids" widget="payment_term_line_ids">
|
||||
<list string="Payment Terms" editable="top">
|
||||
<field name="value_amount"/>
|
||||
|
||||
</list>
|
||||
</field>
|
||||
</form>
|
||||
`,
|
||||
});
|
||||
expect(".o_data_row").toHaveCount(2);
|
||||
// click the add button
|
||||
await contains(".o_field_x2many_list_row_add > a").click();
|
||||
// make sure the new record is added
|
||||
expect(".o_data_row").toHaveCount(3);
|
||||
// global click
|
||||
await contains(".o_form_view").click();
|
||||
// make sure the new record is still there
|
||||
expect(".o_data_row").toHaveCount(3);
|
||||
// click the add button again
|
||||
await contains(".o_field_x2many_list_row_add > a").click();
|
||||
// make sure the new record is added
|
||||
expect(".o_data_row").toHaveCount(4);
|
||||
// click on an existing record
|
||||
await contains(".o_data_row .o_data_cell").click();
|
||||
// make sure the new record is still there
|
||||
expect(".o_data_row").toHaveCount(4);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { editInput, getFixture, patchWithCleanup } from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
|
||||
let target;
|
||||
let serverData;
|
||||
|
||||
QUnit.module("Widgets", (hooks) => {
|
||||
hooks.beforeEach(() => {
|
||||
target = getFixture();
|
||||
serverData = {
|
||||
models: {
|
||||
partner: {
|
||||
fields: {
|
||||
display_name: { string: "Displayed name", type: "char" },
|
||||
type: { string: "Type", type: "char"}
|
||||
},
|
||||
records: [
|
||||
{
|
||||
id: 7,
|
||||
display_name: "first record",
|
||||
type: "purchase",
|
||||
},
|
||||
],
|
||||
onchanges: {},
|
||||
},
|
||||
},
|
||||
views: {
|
||||
"partner,false,form": `<form>
|
||||
<widget name="account_file_uploader"/>
|
||||
<field name="display_name" required="1"/>
|
||||
</form>`,
|
||||
"partner,false,list": `<tree>
|
||||
<field name="id"/>
|
||||
<field name="display_name"/>
|
||||
</tree>`,
|
||||
"partner,false,search": `<search/>`,
|
||||
},
|
||||
};
|
||||
|
||||
setupViewRegistries();
|
||||
});
|
||||
|
||||
QUnit.module("AccountFileUploader");
|
||||
|
||||
QUnit.test("widget contains context based on the record despite field not in view", async function (assert) {
|
||||
|
||||
const form = await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
resId: 7,
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "create") {
|
||||
assert.deepEqual(args.model, "ir.attachment", "create ir.attachment")
|
||||
return 99;
|
||||
}
|
||||
if (args.method === "create_document_from_attachment" && args.model === "account.journal") {
|
||||
assert.equal(args.kwargs.context.default_journal_id, 7, "create documents in correct journal");
|
||||
assert.equal(args.kwargs.context.default_move_type, "in_invoice", "create documents with correct move type");
|
||||
return {
|
||||
'name': 'Generated Documents',
|
||||
'domain': [],
|
||||
'res_model': 'partner',
|
||||
'type': 'ir.actions.act_window',
|
||||
'context': {},
|
||||
'views': [[false, "list"], [false, "form"]],
|
||||
'view_mode': 'list, form',
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
patchWithCleanup(form.env.services.action, {
|
||||
doAction(action) {
|
||||
assert.equal(action.type, "ir.actions.act_window", "do action after documents created");
|
||||
}
|
||||
});
|
||||
|
||||
assert.expect(5);
|
||||
assert.containsOnce(target, '.o_widget_account_file_uploader');
|
||||
const file = new File(["test"], "fake_file.txt", { type: "text/plain" });
|
||||
await editInput(target, ".o_input_file", file);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
import { expect, test } from "@odoo/hoot";
|
||||
import { queryFirst } from "@odoo/hoot-dom";
|
||||
import {
|
||||
contains,
|
||||
defineModels,
|
||||
fieldInput,
|
||||
fields,
|
||||
models,
|
||||
mountView,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
import { defineAccountModels } from "./account_test_helpers";
|
||||
|
||||
class Account extends models.Model {
|
||||
_name = "account.account";
|
||||
_inherit = [];
|
||||
|
||||
code = fields.Char({
|
||||
string: "Code",
|
||||
trim: true,
|
||||
});
|
||||
placeholder_code = fields.Char();
|
||||
|
||||
_records = [
|
||||
{
|
||||
id: 1,
|
||||
placeholder_code: "Placeholder Code",
|
||||
},
|
||||
];
|
||||
|
||||
_views = {
|
||||
list: /* xml */ `
|
||||
<list editable="top" create="1" delete="1">
|
||||
<field name="placeholder_code" column_invisible="1" />
|
||||
<field name="code" widget="char_with_placeholder_field" options="{'placeholder_field': 'placeholder_code'}" />
|
||||
</list>
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
||||
defineAccountModels();
|
||||
defineModels([Account]);
|
||||
test.tags("desktop");
|
||||
test("List: placeholder_field shows as text/placeholder", async () => {
|
||||
await mountView({
|
||||
type: "list",
|
||||
resModel: "account.account",
|
||||
});
|
||||
|
||||
const firstCellSelector = "tbody td:not(.o_list_record_selector):first";
|
||||
|
||||
expect(`${firstCellSelector} span`).toHaveText("Placeholder Code", {
|
||||
message: "placeholder_field should be the text value",
|
||||
});
|
||||
|
||||
expect(`${firstCellSelector} span`).toHaveClass("text-muted", {
|
||||
message: "placeholder_field should be greyed out",
|
||||
});
|
||||
|
||||
await contains(firstCellSelector).click();
|
||||
expect(queryFirst(firstCellSelector).parentElement).toHaveClass("o_selected_row", {
|
||||
message: "should be set as edit mode",
|
||||
});
|
||||
expect(`${firstCellSelector} input`).toHaveValue("", {
|
||||
message: "once in edit mode, should have no value in input",
|
||||
});
|
||||
expect(`${firstCellSelector} input`).toHaveAttribute("placeholder", "Placeholder Code", {
|
||||
message: "once in edit mode, should have placeholder_field as placeholder",
|
||||
});
|
||||
await fieldInput("code").edit("100001", { confirm: false });
|
||||
|
||||
await contains(".o_list_button_save").click();
|
||||
expect(firstCellSelector).toHaveText("100001", {
|
||||
message: "entered value should be saved",
|
||||
});
|
||||
expect(firstCellSelector).not.toHaveClass("text-muted", {
|
||||
message: "field should not be greyed out",
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import { models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class AccountMove extends models.ServerModel {
|
||||
_name = "account.move";
|
||||
|
||||
get_extra_print_items() {
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class AccountMoveLine extends models.ServerModel {
|
||||
_name = "account.move.line";
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,122 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import {
|
||||
click,
|
||||
dragAndDrop,
|
||||
getNodesTextContent,
|
||||
getFixture,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
|
||||
let serverData;
|
||||
let target;
|
||||
|
||||
QUnit.module('section_and_note', (hooks) => {
|
||||
hooks.beforeEach(() => {
|
||||
target = getFixture();
|
||||
serverData = {
|
||||
models: {
|
||||
invoice: {
|
||||
fields: {
|
||||
invoice_line_ids: {
|
||||
string: "Lines",
|
||||
type: 'one2many',
|
||||
relation: 'invoice_line',
|
||||
relation_field: 'invoice_id'
|
||||
},
|
||||
},
|
||||
records: [
|
||||
{id: 1, invoice_line_ids: [1, 2, 3]},
|
||||
],
|
||||
},
|
||||
invoice_line: {
|
||||
fields: {
|
||||
sequence: { string: "sequence", type: "integer", sortable: true },
|
||||
display_type: {
|
||||
string: 'Type',
|
||||
type: 'selection',
|
||||
selection: [['line_section', "Section"], ['line_note', "Note"]]
|
||||
},
|
||||
invoice_id: {
|
||||
string: "Invoice",
|
||||
type: 'many2one',
|
||||
relation: 'invoice'
|
||||
},
|
||||
name: {
|
||||
string: "Name",
|
||||
type: 'text'
|
||||
},
|
||||
price: {
|
||||
string: "Price",
|
||||
type: 'monetary',
|
||||
}
|
||||
},
|
||||
records: [
|
||||
{id: 1, display_type: false, invoice_id: 1, name: 'product\n2 lines', price: 123.45},
|
||||
{id: 2, display_type: 'line_section', invoice_id: 1, name: 'section'},
|
||||
{id: 3, display_type: 'line_note', invoice_id: 1, name: 'note'},
|
||||
]
|
||||
},
|
||||
},
|
||||
};
|
||||
setupViewRegistries();
|
||||
});
|
||||
|
||||
QUnit.test('correct display of section and note fields', async (assert) => {
|
||||
assert.expect(9);
|
||||
await makeView({
|
||||
type: 'form',
|
||||
resModel: 'invoice',
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="invoice_line_ids" widget="section_and_note_one2many">
|
||||
<tree editable="bottom">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="display_type" invisible="1"/>
|
||||
<field name="name" widget="section_and_note_text"/>
|
||||
<field name="price"/>
|
||||
</tree>
|
||||
</field>
|
||||
</form>`,
|
||||
resId: 1,
|
||||
});
|
||||
|
||||
assert.hasClass(target.querySelector('[name="invoice_line_ids"] table'), 'o_section_and_note_list_view');
|
||||
|
||||
// product should be displayed correctly
|
||||
assert.doesNotHaveClass(target.querySelector('tr.o_data_row:nth-child(1'), 'o_is_line_section',
|
||||
"should not have a section class");
|
||||
|
||||
// section should be displayed correctly
|
||||
const section_line = target.querySelector('tr.o_data_row:nth-child(2)');
|
||||
const section_cell = section_line.querySelector('td.o_section_and_note_text_cell');
|
||||
assert.hasClass(section_line, 'o_is_line_section',
|
||||
"should have a section class");
|
||||
assert.hasAttrValue(section_cell, 'colspan', '2')
|
||||
|
||||
// note should be displayed correctly
|
||||
const note_line = target.querySelector('tr.o_data_row:nth-child(3)');
|
||||
const note_cell = note_line.querySelector('td.o_section_and_note_text_cell');
|
||||
assert.hasClass(note_line, 'o_is_line_note',
|
||||
"should have a note class");
|
||||
assert.hasAttrValue(note_cell, 'colspan', '2')
|
||||
|
||||
// editing note line should be textarea
|
||||
await click(note_cell);
|
||||
assert.containsOnce(note_line, 'td.o_section_and_note_text_cell div[name="name"] textarea',
|
||||
"note line should be textarea");
|
||||
|
||||
// editing section line should be input
|
||||
await click(section_cell);
|
||||
assert.containsOnce(section_line, 'td.o_section_and_note_text_cell div[name="name"] input',
|
||||
"section line should be input");
|
||||
|
||||
// Drag and drop the second line in first position
|
||||
await dragAndDrop("tbody tr:nth-child(2) .o_row_handle", "tbody tr:nth-child(1)");
|
||||
assert.deepEqual(
|
||||
getNodesTextContent(target.querySelectorAll(".o_data_cell.o_list_text")),
|
||||
["section", "product\n2 lines", "note"]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
odoo.define('account.dashboard.setup.tour', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var tour = require('web_tour.tour');
|
||||
|
||||
var _t = core._t;
|
||||
|
||||
tour.register('account_render_report', {
|
||||
test: true,
|
||||
url: '/web',
|
||||
}, [tour.stepUtils.showAppsMenuItem(),
|
||||
{
|
||||
id: 'account_menu_click',
|
||||
trigger: '.o_app[data-menu-xmlid="account.menu_finance"]',
|
||||
position: 'bottom',
|
||||
}, {
|
||||
trigger: '.o_data_row:first .o_data_cell',
|
||||
extra_trigger: '.breadcrumb',
|
||||
}, {
|
||||
trigger: '.o_control_panel button:contains("' + _t('Print') + '")',
|
||||
}, {
|
||||
trigger: '.o_control_panel .o-dropdown--menu span:contains("' + _t('Invoices without Payment') + '")',
|
||||
}, {
|
||||
trigger: 'iframe .o_report_layout_standard h2',
|
||||
content: 'Primary color is correct',
|
||||
run: function () {
|
||||
if (this.$anchor.css('color') !== "rgb(18, 52, 86)") {
|
||||
console.error('The primary color should be the one set on the company.');
|
||||
}
|
||||
},
|
||||
}, {
|
||||
trigger: 'iframe .o_report_layout_standard #informations div strong',
|
||||
content: 'Secondary color is correct',
|
||||
run: function () {
|
||||
if (this.$anchor.css('color') !== "rgb(120, 145, 1)") {
|
||||
console.error('The secondary color should be the one set on the company.');
|
||||
}
|
||||
},
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { addSectionFromProductCatalog, showProductColumn } from "@account/js/tours/tour_utils";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("web_tour.tours").add("test_use_product_catalog_on_invoice", {
|
||||
steps: () => [
|
||||
{
|
||||
content: "Click Catalog Button",
|
||||
trigger: "button[name=action_add_from_catalog]",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Add a Product",
|
||||
trigger: ".o_kanban_record:contains(Test Product)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Wait for it",
|
||||
trigger: ".o_product_added",
|
||||
},
|
||||
{
|
||||
content: "Back to Invoice",
|
||||
trigger: ".o-kanban-button-back",
|
||||
run: "click",
|
||||
},
|
||||
...showProductColumn(),
|
||||
{
|
||||
content: "Ensure product is added",
|
||||
trigger: ".o_field_product_label_section_and_note_cell:contains(Test Product)",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add('test_add_section_from_product_catalog_on_invoice', {
|
||||
steps: () => addSectionFromProductCatalog()
|
||||
});
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { registry } from '@web/core/registry';
|
||||
import { stepUtils } from "@web_tour/tour_utils";
|
||||
|
||||
registry.category("web_tour.tours").add("deductible_amount_column", {
|
||||
url: "/odoo/vendor-bills/new",
|
||||
steps: () => [
|
||||
{
|
||||
content: "Add item",
|
||||
trigger: "div[name='invoice_line_ids'] .o_field_x2many_list_row_add a:contains('Add a line')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Edit name",
|
||||
trigger: ".o_field_widget[name='name'] .o_input",
|
||||
run: "edit Laptop"
|
||||
},
|
||||
{
|
||||
content: "Edit deductible amount",
|
||||
trigger: ".o_field_widget[name='deductible_amount'] > .o_input",
|
||||
run: "edit 80"
|
||||
},
|
||||
{
|
||||
content: "Set Bill Date",
|
||||
trigger: "input[data-field=invoice_date]",
|
||||
run: "edit 2025-12-01",
|
||||
},
|
||||
...stepUtils.saveForm(),
|
||||
]})
|
||||
|
|
@ -1,128 +1,147 @@
|
|||
/** @odoo-module alias=account.tax.group.tour.tests */
|
||||
"use strict";
|
||||
import { accountTourSteps } from "@account/js/tours/account";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { stepUtils } from "@web_tour/tour_utils";
|
||||
|
||||
import tour from 'web_tour.tour';
|
||||
|
||||
tour.register('account_tax_group', {
|
||||
test: true,
|
||||
url: "/web",
|
||||
}, [tour.stepUtils.showAppsMenuItem(),
|
||||
{
|
||||
id: 'account_menu_click',
|
||||
content: "Go to Invoicing",
|
||||
trigger: '.o_app[data-menu-xmlid="account.menu_finance"]',
|
||||
},
|
||||
registry.category("web_tour.tours").add('account_tax_group', {
|
||||
url: "/odoo",
|
||||
steps: () => [
|
||||
...accountTourSteps.goToAccountMenu("Go to Invoicing"),
|
||||
{
|
||||
content: "Go to Vendors",
|
||||
trigger: 'span:contains("Vendors")',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Go to Bills",
|
||||
trigger: 'a:contains("Bills")',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_breadcrumb .text-truncate:contains(Bills)",
|
||||
},
|
||||
{
|
||||
extra_trigger: '.breadcrumb:contains("Bills")',
|
||||
content: "Create new bill",
|
||||
trigger: '.o_list_button_add',
|
||||
trigger: '.o_control_panel_main_buttons .o_list_button_add',
|
||||
run: "click",
|
||||
},
|
||||
// Set a vendor
|
||||
{
|
||||
content: "Add vendor",
|
||||
trigger: 'div.o_field_widget.o_field_res_partner_many2one[name="partner_id"] div input',
|
||||
run: 'text Account Tax Group Partner',
|
||||
run: "edit Account Tax Group Partner",
|
||||
},
|
||||
{
|
||||
content: "Valid vendor",
|
||||
trigger: '.ui-menu-item a:contains("Account Tax Group Partner")',
|
||||
run: "click",
|
||||
},
|
||||
// Show product column
|
||||
{
|
||||
content: "Open line fields list",
|
||||
trigger: ".o_optional_columns_dropdown_toggle",
|
||||
run: "click"
|
||||
},
|
||||
{
|
||||
content: "Show product column",
|
||||
trigger: '.o-dropdown-item input[name="product_id"]',
|
||||
run: "click"
|
||||
},
|
||||
{
|
||||
content: "Close line fields list",
|
||||
trigger: ".o_optional_columns_dropdown_toggle",
|
||||
run: "click"
|
||||
},
|
||||
// Add First product
|
||||
{
|
||||
content: "Add items",
|
||||
trigger: 'div[name="invoice_line_ids"] .o_field_x2many_list_row_add a:contains("Add a line")',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Select input",
|
||||
trigger: 'div[name="invoice_line_ids"] .o_selected_row .o_list_many2one[name="product_id"] input',
|
||||
},
|
||||
{
|
||||
content: "Type item",
|
||||
trigger: 'div[name="invoice_line_ids"] .o_selected_row .o_list_many2one[name="product_id"] input',
|
||||
run: "text Account Tax Group Product",
|
||||
run: "edit Account Tax Group Product",
|
||||
},
|
||||
{
|
||||
content: "Valid item",
|
||||
trigger: '.ui-menu-item-wrapper:contains("Account Tax Group Product")',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Set Bill Date",
|
||||
trigger: "input[data-field=invoice_date]",
|
||||
run: "edit 2025-12-01",
|
||||
},
|
||||
// Save account.move
|
||||
{
|
||||
content: "Save the account move",
|
||||
trigger: '.o_form_button_save',
|
||||
},
|
||||
...tour.stepUtils.saveForm(),
|
||||
...stepUtils.saveForm(),
|
||||
// Edit tax group amount
|
||||
{
|
||||
content: "Edit tax group amount",
|
||||
trigger: '.o_tax_group_edit',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Modify the input value",
|
||||
trigger: '.o_tax_group_edit_input input',
|
||||
run: function (actions) {
|
||||
$('.o_tax_group_edit_input input').val(200);
|
||||
$('.o_tax_group_edit_input input').select();
|
||||
$('.o_tax_group_edit_input input').blur();
|
||||
run() {
|
||||
this.anchor.value = 200;
|
||||
this.anchor.select();
|
||||
this.anchor.blur();
|
||||
},
|
||||
},
|
||||
// Check new value for total (with modified tax_group_amount).
|
||||
{
|
||||
content: "Valid total amount",
|
||||
trigger: 'span[name="amount_total"]:contains("800")',
|
||||
run: "click",
|
||||
},
|
||||
// Modify the quantity of the object
|
||||
{
|
||||
content: "Select item quantity",
|
||||
trigger: 'div[name="invoice_line_ids"] tbody tr.o_data_row .o_list_number[name="quantity"]',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Change item quantity",
|
||||
trigger: 'div[name="invoice_line_ids"] tbody tr.o_data_row .o_list_number[name="quantity"] input',
|
||||
run: 'text 2',
|
||||
run: "edit 2",
|
||||
},
|
||||
{
|
||||
content: "Valid the new value",
|
||||
trigger: 'div[name="invoice_line_ids"] tbody tr.o_data_row .o_list_number[name="quantity"] input',
|
||||
run: function (actions) {
|
||||
let keydownEvent = jQuery.Event('keydown');
|
||||
keydownEvent.which = 13;
|
||||
this.$anchor.trigger(keydownEvent);
|
||||
},
|
||||
run: "press Enter",
|
||||
},
|
||||
// Save form
|
||||
{
|
||||
content: "Save the account move",
|
||||
trigger: '.o_form_button_save',
|
||||
},
|
||||
...tour.stepUtils.saveForm(),
|
||||
// Check new tax group value
|
||||
{
|
||||
content: "Check new value of tax group",
|
||||
trigger: '.o_tax_group_amount_value:contains("120")',
|
||||
run: "click",
|
||||
},
|
||||
// Save form
|
||||
...stepUtils.saveForm(),
|
||||
// Check new tax group value
|
||||
{
|
||||
content: "Check new value of tax group",
|
||||
trigger: '.o_tax_group_amount_value:contains("120")',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Edit tax value",
|
||||
trigger: '.o_tax_group_edit_input input',
|
||||
run: 'text 2'
|
||||
run: "edit 2 && click body",
|
||||
},
|
||||
{
|
||||
content: "Check new value of total",
|
||||
trigger: '.oe_subtotal_footer_separator:contains("1,202")',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Discard changes",
|
||||
trigger: '.o_form_button_cancel',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Check tax value is reset",
|
||||
trigger: '.o_tax_group_amount_value:contains("120")',
|
||||
},
|
||||
]);
|
||||
]});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("web_tour.tours").add('tests_shared_js_python', {
|
||||
url: "/account/init_tests_shared_js_python",
|
||||
steps: () => [
|
||||
{
|
||||
content: "Click",
|
||||
trigger: 'button',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Wait",
|
||||
trigger: 'button.text-success',
|
||||
timeout: 3000,
|
||||
},
|
||||
]});
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
import { defineMailModels } from "@mail/../tests/mail_test_helpers";
|
||||
import { expect, test } from "@odoo/hoot";
|
||||
import {
|
||||
contains,
|
||||
defineModels,
|
||||
fields,
|
||||
mockService,
|
||||
models,
|
||||
mountView,
|
||||
onRpc,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
class AccountMove extends models.Model {
|
||||
_name = "account.move";
|
||||
|
||||
name = fields.Char();
|
||||
duplicated_ref_ids = fields.Many2many({
|
||||
string: "Duplicated Bills",
|
||||
relation: "account.move",
|
||||
});
|
||||
ref = fields.Char({ string: "Bill Reference" });
|
||||
|
||||
_records = [
|
||||
// for the sake of mocking data, we don't care about the consistency of duplicated refs across records
|
||||
{ id: 1, display_name: "Bill 1", duplicated_ref_ids: [2, 3], ref: "b1" },
|
||||
{ id: 2, display_name: "Bill 2", duplicated_ref_ids: [1], ref: "b2" },
|
||||
{ id: 3, display_name: "Bill 3", duplicated_ref_ids: [1], ref: "b3" },
|
||||
{ id: 4, display_name: "Bill 4", duplicated_ref_ids: [1, 2, 3], ref: "b4" },
|
||||
{ id: 5, display_name: "Bill 5", duplicated_ref_ids: [], ref: "b5" },
|
||||
{ id: 6, display_name: "Bill 6", duplicated_ref_ids: [1, 2, 3, 4, 5], ref: "b6" },
|
||||
];
|
||||
|
||||
_views = {
|
||||
form: `
|
||||
<form>
|
||||
<field name="display_name"/>
|
||||
<field name="ref"/>
|
||||
<field name="duplicated_ref_ids" widget="x2many_buttons"/>
|
||||
</form>
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
||||
defineModels([AccountMove]);
|
||||
defineMailModels();
|
||||
|
||||
test("component rendering: less than 3 records on field", async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
await mountView({
|
||||
resModel: "account.move",
|
||||
resId: 1,
|
||||
type: "form",
|
||||
});
|
||||
expect(".o_field_x2many_buttons").toHaveCount(1);
|
||||
expect(".o_field_x2many_buttons button").toHaveCount(2);
|
||||
});
|
||||
|
||||
test("component rendering: exactly 3 records on field", async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
await mountView({
|
||||
resModel: "account.move",
|
||||
resId: 4,
|
||||
type: "form",
|
||||
});
|
||||
expect(".o_field_x2many_buttons").toHaveCount(1);
|
||||
expect(".o_field_x2many_buttons button").toHaveCount(3);
|
||||
});
|
||||
|
||||
test("component rendering: more than 3 records on field", async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
await mountView({
|
||||
resModel: "account.move",
|
||||
resId: 6,
|
||||
type: "form",
|
||||
});
|
||||
expect(".o_field_x2many_buttons").toHaveCount(1);
|
||||
expect(".o_field_x2many_buttons button").toHaveCount(4);
|
||||
expect(".o_field_x2many_buttons button:eq(3)").toHaveText("... (View all)");
|
||||
});
|
||||
|
||||
test("edit record and check if edits get discarded when click on one of the buttons and redirects to proper record", async () => {
|
||||
onRpc("account.move", "action_open_business_doc", ({ args }) => {
|
||||
expect.step("action_open_business_doc");
|
||||
expect(args.length).toBe(1);
|
||||
expect(args[0]).toBe(2);
|
||||
return {
|
||||
res_model: "account.move",
|
||||
res_id: 2,
|
||||
type: "ir.actions.act_window",
|
||||
views: [[false, "form"]],
|
||||
};
|
||||
});
|
||||
await mountView({
|
||||
resModel: "account.move",
|
||||
resId: 1,
|
||||
type: "form",
|
||||
});
|
||||
|
||||
await contains("[name='ref'] input").edit("new ref");
|
||||
expect("[name='ref'] input").toHaveValue("new ref");
|
||||
await contains(".o_field_x2many_buttons button").click();
|
||||
expect("[name='ref'] input").toHaveValue("b1");
|
||||
expect.verifySteps(["action_open_business_doc"]);
|
||||
});
|
||||
|
||||
// test if clicking on last button redirects to records in list view
|
||||
test("redirect to list view and discards edits when clicking on last button with more than 3 records on field", async () => {
|
||||
expect.assertions(3);
|
||||
mockService("action", {
|
||||
doAction(action) {
|
||||
expect(action).toEqual({
|
||||
domain: [["id", "in", [1, 2, 3, 4, 5]]],
|
||||
name: "Duplicated Bills",
|
||||
res_model: "account.move",
|
||||
type: "ir.actions.act_window",
|
||||
views: [
|
||||
[false, "list"],
|
||||
[false, "form"],
|
||||
],
|
||||
context: {
|
||||
list_view_ref: "account.view_duplicated_moves_tree_js",
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
await mountView({
|
||||
resModel: "account.move",
|
||||
resId: 6,
|
||||
type: "form",
|
||||
});
|
||||
await contains("[name='ref'] input").edit("new ref");
|
||||
expect("[name='ref'] input").toHaveValue("new ref");
|
||||
await contains(".o_field_x2many_buttons button:eq(3)").click();
|
||||
expect("[name='ref'] input").toHaveValue("b6");
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue