[REF] account: convert fields to owl

This commit converts the following fields to owl:
 - account_resequence_widget
 - grouped_view_widget
 - account-tax-totals-field

Part-of: odoo/odoo#78221
Co-authored-by: luvi <luvi@odoo.com>
This commit is contained in:
Simon Genin (ges)
2022-07-22 16:21:42 +02:00
committed by Géry Debongnie
co-authored by luvi
parent 24e8e10b34
commit 350ad39d51
11 changed files with 392 additions and 148 deletions
+3
View File
@@ -93,15 +93,18 @@ You could use this simplified accounting in case you work with an (external) acc
'account/static/src/scss/account_reconciliation.scss',
'account/static/src/scss/account_journal_dashboard.scss',
'account/static/src/scss/account_dashboard.scss',
'account/static/src/scss/account_resequence.scss',
'account/static/src/scss/account_searchpanel.scss',
'account/static/src/scss/section_and_note_backend.scss',
'account/static/src/scss/account_activity.scss',
'account/static/src/scss/bills_upload.scss',
'account/static/src/scss/group_view_widget.scss',
'account/static/src/js/account_payment_field.js',
'account/static/src/js/account_resequence_field.js',
'account/static/src/js/grouped_view_widget.js',
'account/static/src/js/mail_activity.js',
'account/static/src/js/tax_totals.js',
'account/static/src/js/legacy_tax_totals.js',
'account/static/src/js/section_and_note_fields_backend.js',
'account/static/src/js/tours/account.js',
'account/static/src/js/bills_upload.js',
@@ -1,31 +1,24 @@
odoo.define('account.ShowResequenceRenderer', function (require) {
"use strict";
/** @odoo-module */
const AbstractFieldOwl = require('web.AbstractFieldOwl');
const field_registry = require('web.field_registry_owl');
import { registry } from "@web/core/registry";
const { Component, onWillUpdateProps } = owl;
class ChangeLine extends Component { }
ChangeLine.template = 'account.ResequenceChangeLine';
ChangeLine.props = ["changeLine", 'ordering'];
class ChangeLine extends Component {}
ChangeLine.template = "account.ResequenceChangeLine";
ChangeLine.props = ["changeLine", "ordering"];
class ShowResequenceRenderer extends AbstractFieldOwl {
class ShowResequenceRenderer extends Component {
setup() {
super.setup();
this.data = this.value ? JSON.parse(this.value) : {
changeLines: [],
ordering: 'date',
};
onWillUpdateProps(() => {
Object.assign(this.data, JSON.parse(this.value));
});
this.formatData(this.props);
onWillUpdateProps((nextProps) => this.formatData(nextProps));
}
formatData(props) {
this.data = props.value ? JSON.parse(props.value) : { changeLines: [], ordering: "date" };
}
}
ShowResequenceRenderer.template = 'account.ResequenceRenderer';
ShowResequenceRenderer.components = { ChangeLine }
ShowResequenceRenderer.template = "account.ResequenceRenderer";
ShowResequenceRenderer.components = { ChangeLine };
field_registry.add('account_resequence_widget', ShowResequenceRenderer);
return ShowResequenceRenderer;
});
registry.category("fields").add("account_resequence_widget", ShowResequenceRenderer);
@@ -1,39 +1,31 @@
odoo.define('account.ShowGroupedList', function (require) {
"use strict";
/** @odoo-module */
const AbstractFieldOwl = require('web.AbstractFieldOwl');
const field_registry = require('web.field_registry_owl');
import { registry } from "@web/core/registry";
const { Component, onWillUpdateProps } = owl;
class ListItem extends Component { }
ListItem.template = 'account.GroupedItemTemplate';
class ListItem extends Component {}
ListItem.template = "account.GroupedItemTemplate";
ListItem.props = ["item_vals", "options"];
class ListGroup extends Component { }
ListGroup.template = 'account.GroupedItemsTemplate';
ListGroup.components = { ListItem }
class ListGroup extends Component {}
ListGroup.template = "account.GroupedItemsTemplate";
ListGroup.components = { ListItem };
ListGroup.props = ["group_vals", "options"];
class ShowGroupedList extends AbstractFieldOwl {
class ShowGroupedList extends Component {
setup() {
super.setup();
this.data = this.value ? JSON.parse(this.value) : {
groups_vals: [],
options: {
discarded_number: '',
columns: [],
},
};
onWillUpdateProps(() => {
Object.assign(this.data, JSON.parse(this.value));
});
this.formatData(this.props);
onWillUpdateProps((nextProps) => this.formatData(nextProps));
}
formatData(props) {
this.data = props.value
? JSON.parse(props.value)
: { groups_vals: [], options: { discarded_number: "", columns: [] } };
}
}
ShowGroupedList.template = 'account.GroupedListTemplate';
ShowGroupedList.components = { ListGroup }
ShowGroupedList.template = "account.GroupedListTemplate";
ShowGroupedList.components = { ListGroup };
field_registry.add('grouped_view_widget', ShowGroupedList);
return ShowGroupedList;
});
registry.category("fields").add("grouped_view_widget", ShowGroupedList);
@@ -0,0 +1,154 @@
/** @odoo-module alias=account.tax_group_owl **/
"use strict";
import session from 'web.session';
import AbstractFieldOwl from 'web.AbstractFieldOwl';
import fieldUtils from 'web.field_utils';
import field_registry from 'web.field_registry_owl';
import { LegacyComponent } from "@web/legacy/legacy_component";
const { onPatched, onWillUpdateProps, useRef, useState } = owl;
/**
A line of some LegacyTaxTotalsComponent, giving the values of a tax group.
**/
class LegacyTaxGroupComponent extends LegacyComponent {
setup() {
this.inputTax = useRef('taxValueInput');
this.state = useState({value: 'readonly'});
this.allowTaxEdition = this.props.mode === 'edit' ? this.props.allowTaxEdition : false;
onPatched(this.onPatched);
onWillUpdateProps(this.onWillUpdateProps);
}
//--------------------------------------------------------------------------
// Life cycle methods
//--------------------------------------------------------------------------
onWillUpdateProps(nextProps) {
this.setState('readonly'); // If props are edited, we set the state to readonly
}
onPatched() {
if (this.state.value === 'edit') {
this.inputTax.el.focus(); // Focus the input
this.inputTax.el.value = this.props.taxGroup.tax_group_amount;
}
}
//--------------------------------------------------------------------------
// Main methods
//--------------------------------------------------------------------------
/**
* 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';
}
}
/**
* 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); // The records using this widget must have a currency_id field.
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.props.taxGroup.tax_group_amount = newValue;
this.props.onChangeTaxGroup({
oldValue: this.props.taxGroup.tax_group_amount,
newValue: newValue,
taxGroupId: this.props.taxGroup.tax_group_id
});
}
}
LegacyTaxGroupComponent.props = ['taxGroup', 'allowTaxEdition', 'record', 'onChangeTaxGroup', 'mode'];
LegacyTaxGroupComponent.template = 'account.LegacyTaxGroupComponent';
/**
Widget used to display tax totals by tax groups for invoices, PO and SO,
and possibly allowing editing them.
Note that this widget requires the object it is used on to have a
currency_id field.
**/
class LegacyTaxTotalsComponent extends AbstractFieldOwl {
setup() {
super.setup();
this.totals = useState({value: this.value ? JSON.parse(this.value) : null});
this.allowTaxEdition = this.nodeOptions['allowTaxEdition'];
onWillUpdateProps(this.onWillUpdateProps);
}
onWillUpdateProps(nextProps) {
// We only reformat tax groups if there are changed
this.totals.value = JSON.parse(nextProps.record.data[this.props.fieldName]);
}
_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();
}
}
/**
* This method is the main function of the tax group widget.
* It is called by an event trigger (from the LegacyTaxGroupComponent) 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.
*/
_onChangeTaxValueByTaxGroup(ev) {
this.trigger('field-changed', {
dataPointID: this.record.id,
changes: { tax_totals_json: JSON.stringify(this.totals.value) }
})
}
}
LegacyTaxTotalsComponent.template = 'account.LegacyTaxTotalsField';
LegacyTaxTotalsComponent.components = { LegacyTaxGroupComponent };
field_registry.add('account-tax-totals-field', LegacyTaxTotalsComponent);
export default LegacyTaxTotalsComponent
+85 -74
View File
@@ -1,43 +1,34 @@
/** @odoo-module alias=account.tax_group_owl **/
"use strict";
/** @odoo-module **/
import session from 'web.session';
import AbstractFieldOwl from 'web.AbstractFieldOwl';
import fieldUtils from 'web.field_utils';
import field_registry from 'web.field_registry_owl';
import { LegacyComponent } from "@web/legacy/legacy_component";
import { formatFloat } from "@web/views/fields/formatters";
import { standardFieldProps } from "@web/views/fields/standard_field_props";
import { registry } from "@web/core/registry";
import { session } from "@web/session";
const { onPatched, onWillUpdateProps, useRef, useState } = owl;
const { Component, onPatched, onWillUpdateProps, useRef, useState } = owl;
/**
A line of some TaxTotalsComponent, giving the values of a tax group.
**/
class TaxGroupComponent extends LegacyComponent {
class TaxGroupComponent extends Component {
setup() {
this.inputTax = useRef('taxValueInput');
this.state = useState({value: 'readonly'});
this.allowTaxEdition = this.props.mode === 'edit' ? this.props.allowTaxEdition : false;
onPatched(this.onPatched);
onWillUpdateProps(this.onWillUpdateProps);
this.inputTax = useRef("taxValueInput");
this.state = useState({value: "readonly"});
onPatched(() => {
if (this.state.value === "edit") {
const { taxGroup, currency } = this.props;
const newVal = formatFloat(taxGroup.tax_group_amount, { digits: currency.digits });
this.inputTax.el.value = newVal;
this.inputTax.el.focus(); // Focus the input
}
});
onWillUpdateProps(() => {
this.setState("readonly");
});
}
//--------------------------------------------------------------------------
// Life cycle methods
//--------------------------------------------------------------------------
onWillUpdateProps(nextProps) {
this.setState('readonly'); // If props are edited, we set the state to readonly
}
onPatched() {
if (this.state.value === 'edit') {
let newValue = this.props.taxGroup.tax_group_amount;
let currency = session.get_currency(this.props.record.data.currency_id.data.id);
newValue = fieldUtils.format.float(newValue, null, {digits: currency.digits});
this.inputTax.el.focus(); // Focus the input
this.inputTax.el.value = newValue;
}
get allowTaxEdition() {
return !this.props.isReadonly && this.props.allowTaxEdition;
}
//--------------------------------------------------------------------------
@@ -57,11 +48,11 @@ class TaxGroupComponent extends LegacyComponent {
* @param {String} value
*/
setState(value) {
if (['readonly', 'edit', 'disable'].includes(value)) {
if (["readonly", "edit", "disable"].includes(value)) {
this.state.value = value;
}
else {
this.state.value = 'readonly';
this.state.value = "readonly";
}
}
@@ -75,33 +66,40 @@ class TaxGroupComponent extends LegacyComponent {
* 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); // The records using this widget must have a currency_id field.
this.setState("disable"); // Disable the input
let newValue = Number(this.inputTax.el.value); // Get the new value
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
newValue = formatFloat(newValue, { digits: this.props.currency.digits }); // Return a string rounded to currency precision
} catch (_err) {
$(this.inputTax.el).addClass('o_field_invalid');
this.setState('edit');
this.props.invalidate();
this.setState("edit");
return;
}
// The newValue can't be equals to 0
// The newValue can"t be equals to 0
if (newValue === this.props.taxGroup.tax_group_amount || newValue === 0) {
this.setState('readonly');
this.setState("readonly");
return;
}
const oldValue = this.props.taxGroup.tax_group_amount;
this.props.taxGroup.tax_group_amount = newValue;
this.props.taxGroup.formatted_tax_group_amount = this.props.taxGroup.formatted_tax_group_amount.replace(oldValue.toString(), newValue.toString());
this.props.onChangeTaxGroup({
oldValue: this.props.taxGroup.tax_group_amount,
newValue: newValue,
oldValue,
newValue: Number(newValue),
taxGroupId: this.props.taxGroup.tax_group_id
});
}
}
TaxGroupComponent.props = ['taxGroup', 'allowTaxEdition', 'record', 'onChangeTaxGroup', 'mode'];
TaxGroupComponent.template = 'account.TaxGroupComponent';
TaxGroupComponent.props = {
currency: {},
taxGroup: { optional: true },
allowTaxEdition: { optional: true },
onChangeTaxGroup: { optional: true },
isReadonly: Boolean,
invalidate: Function,
};
TaxGroupComponent.template = "account.TaxGroupComponent";
/**
Widget used to display tax totals by tax groups for invoices, PO and SO,
@@ -110,49 +108,62 @@ TaxGroupComponent.template = 'account.TaxGroupComponent';
Note that this widget requires the object it is used on to have a
currency_id field.
**/
class TaxTotalsComponent extends AbstractFieldOwl {
export class TaxTotalsComponent extends Component {
setup() {
super.setup();
this.totals = useState({value: this.value ? JSON.parse(this.value) : null});
this.allowTaxEdition = this.nodeOptions['allowTaxEdition'];
onWillUpdateProps(this.onWillUpdateProps);
this.totals = this.parseValue();
onWillUpdateProps((nextProps) => {
// We only reformat tax groups if there are changed
this.totals = this.parseValue();
});
}
onWillUpdateProps(nextProps) {
// We only reformat tax groups if there are changed
this.totals.value = JSON.parse(nextProps.record.data[this.props.fieldName]);
get allowTaxEdition() {
return this.totals && this.totals.allow_tax_edition;
}
_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();
get currency() {
const recordCurrency = this.props.record.data.currency_id;
let currencyId;
if (recordCurrency) {
currencyId = recordCurrency[0];
} else {
currencyId = session.company_currency_id;
}
return session.currencies[currencyId];
}
parseValue() {
if (this.props.record.data[this.props.name]) {
return JSON.parse(this.props.record.data[this.props.name]);
}
return null;
}
invalidate() {
return this.props.record.setInvalidField(this.props.name);
}
/**
* 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 called by the TaxGroupComponent and receives the
* newer tax value.
*
* It is responsible for calculating taxes based on tax groups and triggering
* an event to notify the ORM of a change.
*/
_onChangeTaxValueByTaxGroup(ev) {
this.trigger('field-changed', {
dataPointID: this.record.id,
changes: { tax_totals_json: JSON.stringify(this.totals.value) }
})
_onChangeTaxValueByTaxGroup({ oldValue, newValue, taxGroupId }) {
if (oldValue === newValue) return;
const [oldTotal, newTotal] = [this.totals.amount_total, this.totals.amount_untaxed + newValue];
this.totals.amount_total = newTotal;
this.totals.formatted_amount_total = this.totals.formatted_amount_total.replace(oldTotal.toString(), newTotal.toString());
this.props.update(JSON.stringify(this.totals));
}
}
TaxTotalsComponent.template = 'account.TaxTotalsField';
TaxTotalsComponent.template = "account.TaxTotalsField";
TaxTotalsComponent.components = { TaxGroupComponent };
TaxTotalsComponent.props = {
...standardFieldProps,
}
field_registry.add('account-tax-totals-field', TaxTotalsComponent);
export default TaxTotalsComponent
registry.category("fields").add("account-tax-totals-field", TaxTotalsComponent);
@@ -0,0 +1,3 @@
.o_field_account_resequence_widget {
display: block;
}
@@ -0,0 +1,3 @@
.o_field_grouped_view_widget {
display: block;
}
@@ -1,18 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<templates>
<div t-name="account.ResequenceRenderer" owl="1" class="d-block">
<table t-if="data.changeLines.length" class="table table-sm">
<thead><tr>
<th>Date</th>
<th>Before</th>
<th>After</th>
</tr></thead>
<tbody t-foreach="data.changeLines" t-as="changeLine" t-key="changeLine.id">
<ChangeLine changeLine="changeLine" ordering="data.ordering"/>
</tbody>
</table>
</div>
<t t-name="account.ResequenceRenderer" owl="1" >
<table t-if="data.changeLines.length" class="table table-sm">
<thead><tr>
<th>Date</th>
<th>Before</th>
<th>After</th>
</tr></thead>
<tbody t-foreach="data.changeLines" t-as="changeLine" t-key="changeLine.id">
<ChangeLine changeLine="changeLine" ordering="data.ordering"/>
</tbody>
</table>
</t>
<t t-name="account.ResequenceChangeLine" owl="1">
<tr>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<templates>
<div t-name="account.GroupedListTemplate" owl="1" class="d-block">
<t t-name="account.GroupedListTemplate" owl="1">
<table t-if="data.groups_vals.length" class="table table-sm o_list_table table table-sm table-hover table-striped o_list_table_grouped">
<thead><tr>
<t t-foreach="data.options.columns" t-as="col" t-key="col_index">
@@ -14,7 +14,7 @@
<t t-if="data.options.discarded_number">
<span><t t-esc="data.options.discarded_number"/> are not shown in the preview</span>
</t>
</div>
</t>
<tbody t-name="account.GroupedItemsTemplate" owl="1">
<tr style="background-color: #dee2e6;">
@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<templates>
<t t-name="account.LegacyTaxGroupComponent" owl="1">
<tr>
<td class="o_td_label">
<label class="o_form_label o_tax_total_label" t-esc="props.taxGroup.tax_group_name"/>
</td>
<td>
<t t-if="allowTaxEdition">
<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="() => this.setState('edit')">
<i class="fa fa-pencil"/>
<span class="o_tax_group_amount_value">
<t t-esc="props.taxGroup.formatted_tax_group_amount"/>
</span>
</span>
</t>
</t>
<t t-else="">
<span class="o_tax_group_amount_value">
<t t-esc="props.taxGroup.formatted_tax_group_amount" style="white-space: nowrap;"/>
</span>
</t>
</td>
</tr>
</t>
<div t-name="account.LegacyTaxTotalsField" owl="1">
<table t-if="totals.value" class="oe_right">
<tbody>
<t t-foreach="totals.value.subtotals" t-as="subtotal" t-key="subtotal['name']">
<tr>
<td class="o_td_label">
<label class="o_form_label o_tax_total_label" t-esc="subtotal['name']"/>
</td>
<td>
<span t-att-name="subtotal['name']" style="white-space: nowrap; font-weight: bold;" t-esc="subtotal['formatted_amount']"/>
</td>
</tr>
<t t-foreach="totals.value.groups_by_subtotal[subtotal['name']]" t-as="taxGroup" t-key="taxGroup.group_key">
<LegacyTaxGroupComponent
taxGroup="taxGroup"
record="record"
mode="mode"
allowTaxEdition="totals.value.allow_tax_edition"
onChangeTaxGroup.bind="_onChangeTaxValueByTaxGroup"
/>
</t>
</t>
<!-- Total amount with all taxes-->
<tr>
<td class="o_td_label">
<label class="o_form_label o_tax_total_label">Total</label>
</td>
<td>
<span
name="amount_total"
t-att-class="Object.keys(totals.value.groups_by_subtotal).length > 0 ? 'oe_subtotal_footer_separator' : ''"
t-esc="totals.value.formatted_amount_total"
style="white-space: nowrap; font-weight: bold; font-size: 1.3em;"
/>
</td>
</tr>
</tbody>
</table>
</div>
</templates>
+12 -11
View File
@@ -17,7 +17,7 @@
class="o_field_float
o_field_number o_input"
t-att-disabled="state.value === 'disable'"
t-on-blur.prevent="_onChangeTaxValue"/>
t-on-change.prevent="_onChangeTaxValue"/>
</span>
</t>
<t t-else="">
@@ -38,10 +38,10 @@
</tr>
</t>
<div t-name="account.TaxTotalsField" owl="1">
<table t-if="totals.value" class="oe_right">
<t t-name="account.TaxTotalsField" owl="1">
<table t-if="totals" class="oe_right">
<tbody>
<t t-foreach="totals.value.subtotals" t-as="subtotal" t-key="subtotal['name']">
<t t-foreach="totals.subtotals" t-as="subtotal" t-key="subtotal['name']">
<tr>
<td class="o_td_label">
<label class="o_form_label o_tax_total_label" t-esc="subtotal['name']"/>
@@ -52,13 +52,14 @@
</td>
</tr>
<t t-foreach="totals.value.groups_by_subtotal[subtotal['name']]" t-as="taxGroup" t-key="taxGroup.group_key">
<t t-foreach="totals.groups_by_subtotal[subtotal['name']]" t-as="taxGroup" t-key="taxGroup.group_key">
<TaxGroupComponent
currency="currency"
taxGroup="taxGroup"
record="record"
mode="mode"
allowTaxEdition="totals.value.allow_tax_edition"
isReadonly="props.readonly"
allowTaxEdition="allowTaxEdition"
onChangeTaxGroup.bind="_onChangeTaxValueByTaxGroup"
invalidate="invalidate"
/>
</t>
</t>
@@ -72,13 +73,13 @@
<td>
<span
name="amount_total"
t-att-class="Object.keys(totals.value.groups_by_subtotal).length > 0 ? 'oe_subtotal_footer_separator' : ''"
t-esc="totals.value.formatted_amount_total"
t-att-class="Object.keys(totals.groups_by_subtotal).length > 0 ? 'oe_subtotal_footer_separator' : ''"
t-esc="totals.formatted_amount_total"
style="white-space: nowrap; font-weight: bold; font-size: 1.3em;"
/>
</td>
</tr>
</tbody>
</table>
</div>
</t>
</templates>