[IMP] account: Refactor tax group widget with OWL
Refactor the tax group widget with the new JavaScript Odoo Framework: OWL. This widget has 2 different components, one representing a tax group and one representing the list of tax groups. Task ID: #2312900 Initial Task ID: #1986412 Part-of: odoo/odoo#74138
This commit is contained in:
committed by
oco-odoo
parent
fa1c05e7bb
commit
0f90e80f9e
@@ -238,7 +238,7 @@ class AccountMove(models.Model):
|
||||
compute='_compute_amount', currency_field='currency_id')
|
||||
amount_residual_signed = fields.Monetary(string='Amount Due Signed', store=True,
|
||||
compute='_compute_amount', currency_field='company_currency_id')
|
||||
amount_by_group = fields.Binary(string="Tax amount by group",
|
||||
amount_by_group = fields.Char(string="Tax amount by group",
|
||||
compute='_compute_invoice_taxes_by_group',
|
||||
help='Edit Tax amounts if you encounter rounding issues.')
|
||||
payment_state = fields.Selection(PAYMENT_STATE_SELECTION, string="Payment Status", store=True,
|
||||
@@ -545,6 +545,28 @@ class AccountMove(models.Model):
|
||||
def _onchange_recompute_dynamic_lines(self):
|
||||
self._recompute_dynamic_lines()
|
||||
|
||||
@api.onchange('amount_by_group')
|
||||
def _onchange_amount_by_group(self):
|
||||
""" This method is triggered by the tax group widget. It allows us to edit the right
|
||||
line_ids according to the edited tax group.
|
||||
"""
|
||||
for move in self:
|
||||
amount_by_groups = json.loads(move.amount_by_group)
|
||||
for amount_by_group in amount_by_groups:
|
||||
tax_lines = move.line_ids.filtered(lambda line: line.tax_group_id and line.tax_group_id.id == amount_by_group['tax_group_id'])
|
||||
if len(tax_lines):
|
||||
first_tax_line = tax_lines[0]
|
||||
tax_group_old_amount = sum(tax_lines.mapped('amount_currency'))
|
||||
delta_amount = tax_group_old_amount - amount_by_group['tax_group_amount']
|
||||
delta_amount *= move.move_type == 'in_invoice' and -1 or 1
|
||||
|
||||
if delta_amount != 0:
|
||||
first_tax_line.amount_currency = first_tax_line.amount_currency + delta_amount
|
||||
# We have to trigger the on change manually because we don"t change the value of
|
||||
# amount_currency in the view.
|
||||
first_tax_line._onchange_amount_currency()
|
||||
move._recompute_dynamic_lines()
|
||||
|
||||
@api.model
|
||||
def _get_tax_grouping_key_from_tax_line(self, tax_line):
|
||||
''' Create the dictionary based on a tax line that will be used as key to group taxes together.
|
||||
@@ -1597,7 +1619,7 @@ class AccountMove(models.Model):
|
||||
else:
|
||||
move.invoice_payments_widget = json.dumps(False)
|
||||
|
||||
@api.depends('line_ids.price_subtotal', 'line_ids.tax_base_amount', 'line_ids.tax_line_id', 'partner_id', 'currency_id')
|
||||
@api.depends('line_ids.amount_currency', 'line_ids.tax_base_amount', 'line_ids.tax_line_id', 'partner_id', 'currency_id')
|
||||
def _compute_invoice_taxes_by_group(self):
|
||||
for move in self:
|
||||
|
||||
|
||||
@@ -1,25 +1,33 @@
|
||||
.oe_tax_group_editable {
|
||||
.o_tax_group_editable {
|
||||
width: 100%;
|
||||
padding: 1% 0% !important;
|
||||
}
|
||||
|
||||
.tax_group_edit {
|
||||
.o_tax_group_edit {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tax_group_edit:hover {
|
||||
.o_tax_group_edit:hover {
|
||||
color: #00A09D;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.oe_tax_group_name {
|
||||
font-weight: bold;
|
||||
.o_tax_group_name {
|
||||
font-weight: bold;
|
||||
min-width: 150px;
|
||||
text-align: right;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.oe_tax_group_editable .oe_tax_group_amount_value input {
|
||||
width: 65%;
|
||||
float: right;
|
||||
.o_tax_group_name label {
|
||||
padding-top: 2%;
|
||||
}
|
||||
|
||||
.o_tax_group_editable .o_tax_group_amount_value input {
|
||||
width: 65%;
|
||||
float: right;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.o_tax_group_editable .o_tax_group_amount_value::before {
|
||||
content: ' ';
|
||||
}
|
||||
|
||||
@@ -1,171 +1,190 @@
|
||||
odoo.define('account.tax_group', function (require) {
|
||||
"use strict";
|
||||
/** @odoo-module alias=account.tax_group_owl **/
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var session = require('web.session');
|
||||
var fieldRegistry = require('web.field_registry');
|
||||
var AbstractField = require('web.AbstractField');
|
||||
var fieldUtils = require('web.field_utils');
|
||||
var QWeb = core.qweb;
|
||||
const { Component } = owl;
|
||||
const { useState, useRef } = owl.hooks;
|
||||
import session from 'web.session';
|
||||
import AbstractFieldOwl from 'web.AbstractFieldOwl';
|
||||
import fieldUtils from 'web.field_utils';
|
||||
import field_registry from 'web.field_registry_owl';
|
||||
|
||||
var TaxGroupCustomField = AbstractField.extend({
|
||||
events: {
|
||||
'click .tax_group_edit': '_onClick',
|
||||
'keydown .oe_tax_group_editable .tax_group_edit_input input': '_onKeydown',
|
||||
'blur .oe_tax_group_editable .tax_group_edit_input input': '_onBlur',
|
||||
},
|
||||
class TaxGroupComponent extends Component {
|
||||
constructor(parent, props) {
|
||||
super(parent, props);
|
||||
this.inputTax = useRef('taxValueInput');
|
||||
this.state = useState({value: 'readonly'});
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
//--------------------------------------------------------------------------
|
||||
// Life cycle methods
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* This method is called by "_setTaxGroups". It is
|
||||
* responsible for calculating taxes based on
|
||||
* tax groups and triggering an event to
|
||||
* notify the ORM of a change.
|
||||
*
|
||||
* @param {Id} taxGroupId
|
||||
* @param {Float} deltaAmount
|
||||
*/
|
||||
_changeTaxValueByTaxGroup: function (taxGroupId, deltaAmount) {
|
||||
var self = this;
|
||||
// Search for the first tax line with the same tax group and modify its value
|
||||
var line_id = self.record.data.line_ids.data.find(elem => elem.data.tax_group_id && elem.data.tax_group_id.data.id === taxGroupId);
|
||||
willUpdateProps(nextProps) {
|
||||
this.setState('readonly'); // If props are edited, we set the state to readonly
|
||||
}
|
||||
|
||||
var debitAmount = 0;
|
||||
var creditAmount = 0;
|
||||
var amount_currency = 0;
|
||||
if (line_id.data.currency_id) { // If multi currency enable
|
||||
if (this.record.data.move_type === "in_invoice") {
|
||||
amount_currency = line_id.data.amount_currency - deltaAmount;
|
||||
} else {
|
||||
amount_currency = line_id.data.amount_currency + deltaAmount;
|
||||
}
|
||||
} else {
|
||||
var balance = line_id.data.price_subtotal;
|
||||
balance -= deltaAmount;
|
||||
if (this.record.data.move_type === "in_invoice") { // For vendor bill
|
||||
if (balance > 0) {
|
||||
debitAmount = balance;
|
||||
} else if (balance < 0) {
|
||||
creditAmount = -balance;
|
||||
}
|
||||
} else { // For refund
|
||||
if (balance > 0) {
|
||||
creditAmount = balance;
|
||||
} else if (balance < 0) {
|
||||
debitAmount = -balance;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Trigger ORM
|
||||
self.trigger_up('field_changed', {
|
||||
dataPointID: self.record.id,
|
||||
changes: { line_ids: { operation: "UPDATE", id: line_id.id, data: { amount_currency: amount_currency, debit: debitAmount, credit: creditAmount } } }, // account.move change
|
||||
initialEvent: { dataPointID: line_id.id, changes: { amount_currency: amount_currency, debit: debitAmount, credit: creditAmount }, }, // account.move.line change
|
||||
});
|
||||
},
|
||||
patched() {
|
||||
if (this.state.value === 'edit') {
|
||||
this.inputTax.el.focus(); // Focus the input
|
||||
this.inputTax.el.value = this.props.taxGroup.tax_group_amount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks that the document where the widget
|
||||
* is located is of the "in_invoice" or "in_refund" type.
|
||||
* This makes it possible to know if it is a purchase
|
||||
* document.
|
||||
*
|
||||
* @returns boolean (true if the invoice is a purchase document)
|
||||
*/
|
||||
_isPurchaseDocument: function () {
|
||||
return this.record.data.move_type === "in_invoice" || this.record.data.move_type === 'in_refund';
|
||||
},
|
||||
//--------------------------------------------------------------------------
|
||||
// Main methods
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* This method is part of the widget life cycle and allows you to render
|
||||
* the widget.
|
||||
*
|
||||
* @private
|
||||
* @override
|
||||
*/
|
||||
_render: function () {
|
||||
var self = this;
|
||||
// Display the pencil and allow the event to click and edit only on purchase that are not posted and in edit mode.
|
||||
// since the field is readonly its mode will always be readonly. Therefore we have to use a trick by checking the
|
||||
// formRenderer (the parent) and check if it is in edit in order to know the correct mode.
|
||||
var displayEditWidget = self._isPurchaseDocument() && this.record.data.state === 'draft' && this.getParent().mode === 'edit';
|
||||
this.$el.html($(QWeb.render('AccountTaxGroupTemplate', {
|
||||
lines: self.value,
|
||||
displayEditWidget: displayEditWidget,
|
||||
})));
|
||||
},
|
||||
/**
|
||||
* The purpose of this method is to change the state of the component.
|
||||
* It can have one of the following three states:
|
||||
* - readonly: display in read-only mode of the field,
|
||||
* - edit: display with a html input field,
|
||||
* - disable: display with a html input field that is disabled.
|
||||
*
|
||||
* If a value other than one of these 3 states is passed as a parameter,
|
||||
* the component is set to readonly by default.
|
||||
*
|
||||
* @param {String} value
|
||||
*/
|
||||
setState(value) {
|
||||
if (['readonly', 'edit', 'disable'].includes(value)) {
|
||||
this.state.value = value;
|
||||
}
|
||||
else {
|
||||
this.state.value = 'readonly';
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handler
|
||||
//--------------------------------------------------------------------------
|
||||
/**
|
||||
* This method handles the "_onChangeTaxValue" event. In this method,
|
||||
* we get the new value for the tax group, we format it and we call
|
||||
* the method to recalculate the tax lines. At the moment the method
|
||||
* is called, we disable the html input field.
|
||||
*
|
||||
* In case the value has not changed or the tax group is equal to 0,
|
||||
* the modification does not take place.
|
||||
*/
|
||||
_onChangeTaxValue() {
|
||||
this.setState('disable'); // Disable the input
|
||||
let newValue = this.inputTax.el.value; // Get the new value
|
||||
let currency = session.get_currency(this.props.record.data.currency_id.data.id);
|
||||
try {
|
||||
newValue = fieldUtils.parse.float(newValue); // Need a float for format the value
|
||||
newValue = fieldUtils.format.float(newValue, null, {digits: currency.digits}); // Return a string rounded to currency precision
|
||||
newValue = fieldUtils.parse.float(newValue); // Convert back to Float to compare with oldValue to know if value has changed
|
||||
} catch (err) {
|
||||
$(this.inputTax.el).addClass('o_field_invalid');
|
||||
this.setState('edit');
|
||||
return;
|
||||
}
|
||||
// The newValue can't be equals to 0
|
||||
if (newValue === this.props.taxGroup.tax_group_amount || newValue === 0) {
|
||||
this.setState('readonly');
|
||||
return;
|
||||
}
|
||||
this.trigger('change-tax-group', {
|
||||
oldValue: this.props.taxGroup.tax_group_amount,
|
||||
newValue: newValue,
|
||||
taxGroupId: this.props.taxGroup.tax_group_id
|
||||
});
|
||||
}
|
||||
}
|
||||
TaxGroupComponent.props = ['taxGroup', 'displayEditWidget', 'record'];
|
||||
TaxGroupComponent.template = 'account.TaxGroupComponent';
|
||||
|
||||
/**
|
||||
* This method is called when the user is in edit mode and
|
||||
* leaves the <input> field. Then, we execute the code that
|
||||
* modifies the information.
|
||||
*
|
||||
* @param {event} ev
|
||||
*/
|
||||
_onBlur: function (ev) {
|
||||
ev.preventDefault();
|
||||
var $input = $(ev.target);
|
||||
var newValue = $input.val();
|
||||
var currency = session.get_currency(this.record.data.currency_id.data.id);
|
||||
try {
|
||||
newValue = fieldUtils.parse.float(newValue); // Need a float for format the value.
|
||||
newValue = fieldUtils.format.float(newValue, null, {digits: currency.digits}); // return a string rounded to currency precision
|
||||
newValue = fieldUtils.parse.float(newValue); // convert back to Float to compare with oldValue to know if value has changed
|
||||
} catch (err) {
|
||||
$input.addClass('o_field_invalid');
|
||||
return;
|
||||
}
|
||||
var oldValue = $input.data('originalValue');
|
||||
if (newValue === oldValue || newValue === 0) {
|
||||
return this._render();
|
||||
}
|
||||
var taxGroupId = $input.parents('.oe_tax_group_editable').data('taxGroupId');
|
||||
this._changeTaxValueByTaxGroup(taxGroupId, oldValue-newValue);
|
||||
},
|
||||
class TaxGroupListComponent extends AbstractFieldOwl {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.taxGroups = useState({value: JSON.parse(this.value)});
|
||||
this.displayEditWidget = this._displayEditWidget();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when the user clicks on a specific <td>.
|
||||
* it will hide the edit button and display the field to be edited.
|
||||
*
|
||||
* @param {event} ev
|
||||
*/
|
||||
_onClick: function (ev) {
|
||||
ev.preventDefault();
|
||||
var $taxGroupElement = $(ev.target).parents('.oe_tax_group_editable');
|
||||
// Show input and hide previous element
|
||||
$taxGroupElement.find('.tax_group_edit').addClass('d-none');
|
||||
$taxGroupElement.find('.tax_group_edit_input').removeClass('d-none');
|
||||
var $input = $taxGroupElement.find('.tax_group_edit_input input');
|
||||
// Get original value and display it in user locale in the input
|
||||
var formatedOriginalValue = fieldUtils.format.float($input.data('originalValue'), {}, {});
|
||||
$input.focus(); // Focus the input
|
||||
$input.val(formatedOriginalValue); //add value in user locale to the input
|
||||
},
|
||||
//--------------------------------------------------------------------------
|
||||
// Life cycle method
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* This method is called when the user is in edit mode and pressing
|
||||
* a key on his keyboard. If this key corresponds to ENTER or TAB,
|
||||
* the code that modifies the information is executed.
|
||||
*
|
||||
* @param {event} ev
|
||||
*/
|
||||
_onKeydown: function (ev) {
|
||||
switch (ev.which) {
|
||||
// Trigger only if the user clicks on ENTER or on TAB.
|
||||
case $.ui.keyCode.ENTER:
|
||||
case $.ui.keyCode.TAB:
|
||||
// trigger blur to prevent the code being executed twice
|
||||
$(ev.target).blur();
|
||||
}
|
||||
},
|
||||
willUpdateProps(nextProps) {
|
||||
// We only reformat tax groups if there are changed
|
||||
if (nextProps.fieldName === 'amount_by_group') {
|
||||
this.taxGroups.value = JSON.parse(this.value);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
fieldRegistry.add('tax-group-custom-field', TaxGroupCustomField)
|
||||
});
|
||||
//--------------------------------------------------------------------------
|
||||
// Events
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
_onKeydown(ev) {
|
||||
switch (ev.which) {
|
||||
// Trigger only if the user clicks on ENTER or on TAB.
|
||||
case $.ui.keyCode.ENTER:
|
||||
case $.ui.keyCode.TAB:
|
||||
// trigger blur to prevent the code being executed twice
|
||||
$(ev.target).blur();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private methods
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Tricky method to get the parentWidget. It necessary to do that because
|
||||
* we need to know the view mode. (If we are in readonly or edit).
|
||||
*/
|
||||
_getParentWidget() {
|
||||
return this.__owl__.parent.parentWidget;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks that the document where the widget
|
||||
* is located is of the "in_invoice" or "in_refund" type.
|
||||
* This makes it possible to know if it is a purchase
|
||||
* document.
|
||||
*
|
||||
* @returns boolean (true if the invoice is a purchase document)
|
||||
*/
|
||||
_isPurchaseDocument() {
|
||||
let purchaseMoveTypes = ['in_invoice', 'in_refund'];
|
||||
return purchaseMoveTypes.includes(this.record.data.move_type)
|
||||
}
|
||||
|
||||
/**
|
||||
* This method verifies that the account move is a purchase document, that the document is in draft and
|
||||
* that the edit mode is enabled.
|
||||
*/
|
||||
_displayEditWidget() {
|
||||
return this._isPurchaseDocument() && this.record.data.state === 'draft' && this._getParentWidget().mode === 'edit';
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is the main function of the tax group widget.
|
||||
* It is called by an event trigger (from the TaxGroupComponent) and receives
|
||||
* a particular payload.
|
||||
*
|
||||
* It is responsible for calculating taxes based on tax groups and triggering
|
||||
* an event to notify the ORM of a change.
|
||||
*
|
||||
* @param {*} ev
|
||||
* @param {*} ev.details A payload with the tax group id, the old value of the
|
||||
* tax group and the new value.
|
||||
*/
|
||||
_onChangeTaxValueByTaxGroup(ev) {
|
||||
let detail = ev.detail;
|
||||
this.taxGroups.value.forEach(taxGroup => {
|
||||
if (taxGroup.tax_group_id === detail.taxGroupId) {
|
||||
taxGroup.tax_group_amount = detail.newValue;
|
||||
}
|
||||
});
|
||||
this.trigger('field-changed', {
|
||||
dataPointID: this.record.id,
|
||||
changes: { amount_by_group: JSON.stringify(this.taxGroups.value) }
|
||||
})
|
||||
}
|
||||
}
|
||||
TaxGroupListComponent.template = 'account.TaxGroupCustomField';
|
||||
TaxGroupListComponent.components = { TaxGroupComponent };
|
||||
|
||||
field_registry.add('tax-group-custom-field', TaxGroupListComponent);
|
||||
|
||||
export default TaxGroupListComponent
|
||||
|
||||
@@ -1,35 +1,44 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<templates>
|
||||
<t t-name="AccountTaxGroupTemplate">
|
||||
<table class="o_group o_inner_group oe_subtotal_footer border-0 my-0" style="min-width: 100%;">
|
||||
|
||||
<div t-name="account.TaxGroupCustomField" owl="1">
|
||||
<table class="o_inner_group oe_subtotal_footer border-0 my-O" style="min-width: 100%;">
|
||||
<tbody>
|
||||
<t t-foreach="lines" t-as="line">
|
||||
<t t-foreach="taxGroups.value" t-as="taxGroup">
|
||||
<tr>
|
||||
<td class="o_td_label oe_tax_group_name">
|
||||
<label class="o_form_label" t-esc="line[0]"/>
|
||||
<td class="o_td_label o_tax_group_name">
|
||||
<label class="o_form_label" t-esc="taxGroup.tax_group_name"/>
|
||||
</td>
|
||||
<td class="oe_tax_group_editable" t-att-data-tax-group-id="line[6]">
|
||||
<t t-if="displayEditWidget and line[1] !== 0">
|
||||
<span class="tax_group_edit">
|
||||
<i class="fa fa-pencil"></i>
|
||||
<span class="oe_tax_group_amount_value">
|
||||
<t t-esc="line[3]"/>
|
||||
</span>
|
||||
</span>
|
||||
<span class="tax_group_edit_input d-none">
|
||||
<input type="text" class="o_field_float o_field_number o_input" t-att-data-original-value="line[1]"/>
|
||||
</span>
|
||||
</t>
|
||||
<t t-if="!displayEditWidget or line[1] === 0">
|
||||
<span class="oe_tax_group_amount_value">
|
||||
<t t-esc="line[3]"/>
|
||||
</span>
|
||||
</t>
|
||||
<td class="o_tax_group_editable" t-att-data-tax-group-id="taxGroup.tax_group_id">
|
||||
<TaxGroupComponent taxGroup="taxGroup" record="record" displayEditWidget="displayEditWidget" t-on-change-tax-group="_onChangeTaxValueByTaxGroup"/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<div t-name="account.TaxGroupComponent" owl="1">
|
||||
<t t-if="props.displayEditWidget">
|
||||
<t t-if="['edit', 'disable'].includes(state.value)">
|
||||
<span class="o_tax_group_edit_input">
|
||||
<input type="text" t-ref="taxValueInput" class="o_field_float o_field_number o_input" t-att-disabled="state.value === 'disable'" t-on-blur.prevent="_onChangeTaxValue()"/>
|
||||
</span>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<span class="o_tax_group_edit" t-on-click.prevent="setState('edit')">
|
||||
<i class="fa fa-pencil"></i>
|
||||
<span class="o_tax_group_amount_value">
|
||||
<t t-esc="props.taxGroup.formated_tax_group_amount"/>
|
||||
</span>
|
||||
</span>
|
||||
</t>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<span class="o_tax_group_amount_value">
|
||||
<t t-esc="props.taxGroup.formated_tax_group_amount"/>
|
||||
</span>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -1,124 +1,119 @@
|
||||
odoo.define('account.tax.group.tour.tests', function (require) {
|
||||
"use strict";
|
||||
/** @odoo-module alias=account.tax.group.tour.tests */
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var tour = require('web_tour.tour');
|
||||
var _t = core._t;
|
||||
import tour from 'web_tour.tour';
|
||||
|
||||
tour.register('account_tax_group', {
|
||||
test: true,
|
||||
url: "/web",
|
||||
}, [tour.stepUtils.showAppsMenuItem(),
|
||||
{
|
||||
content: "Go to Invoicing",
|
||||
trigger: '.o_app[data-menu-xmlid="account.menu_finance"]',
|
||||
edition: 'community',
|
||||
tour.register('account_tax_group', {
|
||||
test: true,
|
||||
url: "/web",
|
||||
}, [tour.stepUtils.showAppsMenuItem(),
|
||||
{
|
||||
content: "Go to Invoicing",
|
||||
trigger: '.o_app[data-menu-xmlid="account.menu_finance"]',
|
||||
edition: 'community',
|
||||
},
|
||||
{
|
||||
content: "Go to Accounting",
|
||||
trigger: '.o_app[data-menu-xmlid="account_accountant.menu_accounting"]',
|
||||
edition: 'enterprise',
|
||||
},
|
||||
{
|
||||
content: "Go to Vendors",
|
||||
trigger: 'a:contains("Vendors")',
|
||||
},
|
||||
{
|
||||
content: "Go to Bills",
|
||||
trigger: 'span: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_many2one[name="partner_id"] div input',
|
||||
run: 'text Azure Interior',
|
||||
},
|
||||
{
|
||||
content: "Valid vendor",
|
||||
trigger: '.ui-menu-item a:contains("Azure Interior")',
|
||||
},
|
||||
// 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_list_view .o_selected_row .o_list_many2one:first input',
|
||||
},
|
||||
{
|
||||
content: "Type item",
|
||||
trigger: 'div[name="invoice_line_ids"] .o_list_view .o_selected_row .o_list_many2one:first input',
|
||||
run: "text Large Desk",
|
||||
},
|
||||
{
|
||||
content: "Valid item",
|
||||
trigger: '.ui-menu-item-wrapper:contains("Large Desk")',
|
||||
},
|
||||
// Save account.move
|
||||
{
|
||||
content: "Save the account move",
|
||||
trigger: '.o_form_button_save',
|
||||
},
|
||||
// Edit account.move
|
||||
{
|
||||
content: "Edit the account move",
|
||||
trigger: '.o_form_button_edit',
|
||||
},
|
||||
// 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();
|
||||
},
|
||||
{
|
||||
content: "Go to Accounting",
|
||||
trigger: '.o_app[data-menu-xmlid="account_accountant.menu_accounting"]',
|
||||
edition: 'enterprise',
|
||||
},
|
||||
// Check new value for total (with modified tax_group_amount).
|
||||
{
|
||||
content: "Valid total amount",
|
||||
trigger: 'span[name="amount_total"]:contains("1,499.00")',
|
||||
},
|
||||
// Modify the quantity of the object
|
||||
{
|
||||
content: "Select item quantity",
|
||||
trigger: 'div[name="invoice_line_ids"] .o_list_view tbody tr.o_data_row .o_list_number[title="1.00"]',
|
||||
},
|
||||
{
|
||||
content: "Change item quantity",
|
||||
trigger: 'div[name="invoice_line_ids"] .o_list_view tbody tr.o_data_row .o_list_number[title="1.00"] input',
|
||||
run: 'text 2',
|
||||
},
|
||||
{
|
||||
content: "Valid the new value",
|
||||
trigger: 'div[name="invoice_line_ids"] .o_list_view tbody tr.o_data_row .o_list_number[title="1.00"] input',
|
||||
run: function (actions) {
|
||||
let keydownEvent = jQuery.Event('keydown');
|
||||
keydownEvent.which = 13;
|
||||
this.$anchor.trigger(keydownEvent);
|
||||
},
|
||||
{
|
||||
content: "Go to Vendors",
|
||||
trigger: 'a:contains("Vendors")',
|
||||
},
|
||||
{
|
||||
content: "Go to Bills",
|
||||
trigger: 'span: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_many2one[name="partner_id"] div input',
|
||||
run: 'text Azure Interior',
|
||||
},
|
||||
{
|
||||
content: "Valid vendor",
|
||||
trigger: '.ui-menu-item a:contains("Azure Interior")',
|
||||
},
|
||||
// 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_list_view .o_selected_row .o_list_many2one:first input',
|
||||
},
|
||||
{
|
||||
content: "Type item",
|
||||
trigger: 'div[name="invoice_line_ids"] .o_list_view .o_selected_row .o_list_many2one:first input',
|
||||
run: "text Large Desk",
|
||||
},
|
||||
{
|
||||
content: "Valid item",
|
||||
trigger: '.ui-menu-item-wrapper:contains("Large Desk")',
|
||||
},
|
||||
// Save account.move
|
||||
{
|
||||
content: "Save the account move",
|
||||
trigger: '.o_form_button_save',
|
||||
},
|
||||
// Edit account.move
|
||||
{
|
||||
content: "Edit the account move",
|
||||
trigger: '.o_form_button_edit',
|
||||
},
|
||||
// Edit tax group amount
|
||||
{
|
||||
content: "Edit tax group amount",
|
||||
trigger: '.oe_tax_group_amount_value',
|
||||
},
|
||||
{
|
||||
content: "Modify the input value",
|
||||
trigger: '.tax_group_edit_input input',
|
||||
run: function (actions) {
|
||||
$('.tax_group_edit_input input').val(200);
|
||||
$('.tax_group_edit_input input').select();
|
||||
var keydownEvent = jQuery.Event('keydown');
|
||||
keydownEvent.which = 13;
|
||||
this.$anchor.trigger(keydownEvent);
|
||||
},
|
||||
},
|
||||
// Check new value for total (with modified tax_group_amount).
|
||||
{
|
||||
content: "Valid total amount",
|
||||
trigger: 'span[name="amount_total"]:contains("1,499.00")',
|
||||
},
|
||||
// Modify the quantity of the object
|
||||
{
|
||||
content: "Select item quantity",
|
||||
trigger: 'div[name="invoice_line_ids"] .o_list_view tbody tr.o_data_row .o_list_number[title="1.000"]',
|
||||
},
|
||||
{
|
||||
content: "Change item quantity",
|
||||
trigger: 'div[name="invoice_line_ids"] .o_list_view tbody tr.o_data_row .o_list_number[title="1.000"] input',
|
||||
run: 'text 2',
|
||||
},
|
||||
{
|
||||
content: "Valid the new value",
|
||||
trigger: 'div[name="invoice_line_ids"] .o_list_view tbody tr.o_data_row .o_list_number[title="1.000"] input',
|
||||
run: function (actions) {
|
||||
var keydownEvent = jQuery.Event('keydown');
|
||||
keydownEvent.which = 13;
|
||||
this.$anchor.trigger(keydownEvent);
|
||||
},
|
||||
},
|
||||
// Save form
|
||||
{
|
||||
content: "Save the account move",
|
||||
trigger: '.o_form_button_save',
|
||||
},
|
||||
// Check new tax group value
|
||||
{
|
||||
content: "Check new value of tax group",
|
||||
trigger: '.oe_tax_group_amount_value:contains("389.70")',
|
||||
},
|
||||
]);
|
||||
});
|
||||
},
|
||||
// Save form
|
||||
{
|
||||
content: "Save the account move",
|
||||
trigger: '.o_form_button_save',
|
||||
},
|
||||
// Check new tax group value
|
||||
{
|
||||
content: "Check new value of tax group",
|
||||
trigger: '.o_tax_group_amount_value:contains("389.70")',
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -131,23 +131,23 @@
|
||||
<span t-field="o.amount_untaxed"/>
|
||||
</td>
|
||||
</tr>
|
||||
<t t-foreach="o.amount_by_group" t-as="amount_by_group">
|
||||
<t t-foreach="json.loads(o.amount_by_group)" t-as="amount_by_group">
|
||||
<tr style="">
|
||||
<t t-if="len(o.line_ids.filtered(lambda line: line.tax_line_id)) in [0, 1] and o.amount_untaxed == amount_by_group[2]">
|
||||
<td><span class="text-nowrap" t-esc="amount_by_group[0]"/></td>
|
||||
<t t-if="len(o.line_ids.filtered(lambda line: line.tax_line_id)) in [0, 1] and o.amount_untaxed == amount_by_group['tax_group_base_amount']">
|
||||
<td><span class="text-nowrap" t-esc="amount_by_group['tax_group_name']"/></td>
|
||||
<td class="text-right o_price_total">
|
||||
<span class="text-nowrap" t-esc="amount_by_group[3]" />
|
||||
<span class="text-nowrap" t-esc="amount_by_group['formated_tax_group_amount']" />
|
||||
</td>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<td>
|
||||
<span t-esc="amount_by_group[0]"/>
|
||||
<span t-esc="amount_by_group['tax_group_name']"/>
|
||||
<span class="text-nowrap"> on
|
||||
<t t-esc="amount_by_group[4]"/>
|
||||
<t t-esc="amount_by_group['formated_tax_group_base_amount']"/>
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-right o_price_total">
|
||||
<span class="text-nowrap" t-esc="amount_by_group[3]"/>
|
||||
<span class="text-nowrap" t-esc="amount_by_group['formated_tax_group_amount']"/>
|
||||
</td>
|
||||
</t>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user