Initial commit: Accounting packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:47 +02:00
commit 4ef34c2317
2661 changed files with 1709616 additions and 0 deletions

View file

@ -0,0 +1,78 @@
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();
});
});
});

View file

@ -0,0 +1,87 @@
/** @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);
});
});

View file

@ -0,0 +1,122 @@
/** @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"]
);
});
});

View file

@ -0,0 +1,42 @@
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.');
}
},
}
]);
});

View file

@ -0,0 +1,128 @@
/** @odoo-module alias=account.tax.group.tour.tests */
"use strict";
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"]',
},
{
content: "Go to Vendors",
trigger: 'span:contains("Vendors")',
},
{
content: "Go to Bills",
trigger: 'a:contains("Bills")',
},
{
extra_trigger: '.breadcrumb:contains("Bills")',
content: "Create new bill",
trigger: '.o_list_button_add',
},
// 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',
},
{
content: "Valid vendor",
trigger: '.ui-menu-item a:contains("Account Tax Group Partner")',
},
// Add First product
{
content: "Add items",
trigger: 'div[name="invoice_line_ids"] .o_field_x2many_list_row_add a:contains("Add a line")',
},
{
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",
},
{
content: "Valid item",
trigger: '.ui-menu-item-wrapper:contains("Account Tax Group Product")',
},
// Save account.move
{
content: "Save the account move",
trigger: '.o_form_button_save',
},
...tour.stepUtils.saveForm(),
// Edit tax group amount
{
content: "Edit tax group amount",
trigger: '.o_tax_group_edit',
},
{
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();
},
},
// Check new value for total (with modified tax_group_amount).
{
content: "Valid total amount",
trigger: 'span[name="amount_total"]:contains("800")',
},
// 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"]',
},
{
content: "Change item quantity",
trigger: 'div[name="invoice_line_ids"] tbody tr.o_data_row .o_list_number[name="quantity"] input',
run: 'text 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);
},
},
// 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")',
},
{
content: "Edit tax value",
trigger: '.o_tax_group_edit_input input',
run: 'text 2'
},
{
content: "Check new value of total",
trigger: '.oe_subtotal_footer_separator:contains("1,202")',
},
{
content: "Discard changes",
trigger: '.o_form_button_cancel',
},
{
content: "Check tax value is reset",
trigger: '.o_tax_group_amount_value:contains("120")',
},
]);