[IMP] point_of_sale: Partner Editor Reactivity

At the moment, the partner editor in pos does not use the owl
reactivity system, using instead an onchange function on each
input and manually keeping track of state.

This approach is overcomplicated and leads to bugs.

The necessity of this task first appeared because of one such bug,
namely: the `state` input options not changing in order to reflect
the selected `country`.

Instead of finding a patch for this problem, we decided in this PR
to replace the old logic, making use of `useState` and `t-model`.

closes odoo/odoo#126021

Task: 3323874
Related: odoo/enterprise#43372
Signed-off-by: Joseph Caburnay (jcb) <jcb@odoo.com>
This commit is contained in:
vlst
2023-07-03 11:02:36 +02:00
parent ead6c03405
commit 8278d82fa2
2 changed files with 42 additions and 70 deletions
@@ -4,7 +4,7 @@ import { _t } from "@web/core/l10n/translation";
import { getDataURLFromFile } from "@web/core/utils/urls";
import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup";
import { useService } from "@web/core/utils/hooks";
import { Component } from "@odoo/owl";
import { Component, useState } from "@odoo/owl";
import { usePos } from "@point_of_sale/app/store/pos_hook";
export class PartnerDetailsEdit extends Component {
@@ -15,11 +15,21 @@ export class PartnerDetailsEdit extends Component {
this.pos = usePos();
this.intFields = ["country_id", "state_id", "property_product_pricelist"];
const partner = this.props.partner;
this.changes = {
country_id: partner.country_id && partner.country_id[0],
this.changes = useState({
name: partner.name || "",
street: partner.street || "",
city: partner.city || "",
zip: partner.zip || "",
state_id: partner.state_id && partner.state_id[0],
country_id: partner.country_id && partner.country_id[0],
lang: partner.lang || "",
email: partner.email || "",
phone: partner.phone || "",
mobile: partner.mobile || "",
barcode: partner.barcode || "",
vat: partner.vat || "",
property_product_pricelist: this.setDefaultPricelist(partner),
};
});
Object.assign(this.props.imperativeHandle, {
save: () => this.saveChanges(),
});
@@ -48,12 +58,6 @@ export class PartnerDetailsEdit extends Component {
return false;
}
}
/**
* Save to field `changes` all input changes from the form fields.
*/
captureChange(event) {
this.changes[event.target.name] = event.target.value;
}
saveChanges() {
const processedChanges = {};
for (const [key, value] of Object.entries(this.changes)) {
@@ -63,6 +67,14 @@ export class PartnerDetailsEdit extends Component {
processedChanges[key] = value;
}
}
if (
processedChanges.state_id &&
this.pos.states.find((state) => state.id === processedChanges.state_id)
.country_id[0] !== processedChanges.country_id
) {
processedChanges.state_id = false;
}
if ((!this.props.partner.name && !processedChanges.name) || processedChanges.name === "") {
return this.popup.add(ErrorPopup, {
title: _t("A Customer Name Is Required"),
@@ -1,53 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="point_of_sale.PartnerDetailsEdit" owl="1">
<section class="partner-details edit">
<div class="partner-picture">
<t t-if="partnerImageUrl">
<img t-att-src="partnerImageUrl" alt="Partner"
style="width: 64px; height: 64px; object-fit: cover;" />
<img t-att-src="partnerImageUrl" alt="Partner" style="width: 64px; height: 64px; object-fit: cover;" />
</t>
<t t-else="">
<i class="fa fa-camera" role="img" aria-label="Picture" title="Picture"></i>
</t>
<input type="file" class="image-uploader" t-on-change="uploadImage" />
</div>
<input class="detail partner-name" name="name" t-att-value="props.partner.name or ''"
placeholder="Name" t-on-change="captureChange" />
<input class="detail partner-name" name="name" placeholder="Name" t-model="changes.name" />
<div class="partner-details-box clearfix">
<div class="partner-details-left">
<div class="partner-detail">
<span class="label">Street</span>
<input class="detail" name="street"
t-on-change="captureChange" t-att-value="props.partner.street || ''"
placeholder="Street"
t-att-class="{'border-red': missingFields.includes('street')}"/>
<input class="detail" name="street" t-att-class="{'border-red': missingFields.includes('street')}" t-model="changes.street" placeholder="Street" />
</div>
<div class="partner-detail">
<span class="label">City</span>
<input class="detail" name="city"
t-on-change="captureChange" t-att-value="props.partner.city || ''"
placeholder="City"
t-att-class="{'border-red': missingFields.includes('city')}" />
<input class="detail" name="city" t-att-class="{'border-red': missingFields.includes('city')}" t-model="changes.city" placeholder="City" />
</div>
<div class="partner-detail">
<span class="label">Postcode</span>
<input class="detail" name="zip"
t-on-change="captureChange" t-att-value="props.partner.zip || ''"
placeholder="ZIP"
t-att-class="{'border-red': missingFields.includes('zip')}" />
<input class="detail" name="zip" t-att-class="{'border-red': missingFields.includes('zip')}" t-model="changes.zip" placeholder="ZIP" />
</div>
<div class="partner-detail">
<div class="partner-detail" t-if="pos.states.some((state) => state.country_id[0] == changes.country_id)">
<span class="label">State</span>
<select class="detail" name="state_id"
t-on-change="captureChange"
t-att-class="{'border-red': missingFields.includes('state_id')}">
<select class="detail" name="state_id" t-att-class="{'border-red': missingFields.includes('state_id')}" t-model="changes.state_id">
<option value="">None</option>
<t t-foreach="pos.states" t-as="state" t-key="state.id">
<option t-if="props.partner.country_id[0] == state.country_id[0]"
t-att-value="state.id"
t-att-selected="props.partner.state_id ? ((state.id === props.partner.state_id[0]) ? true : undefined) : undefined">
<option t-if="changes.country_id == state.country_id[0]" t-att-value="state.id">
<t t-esc="state.name" />
</option>
</t>
@@ -55,13 +39,10 @@
</div>
<div class="partner-detail">
<span class="label">Country</span>
<select class="detail" name="country_id"
t-on-change="captureChange"
t-att-class="{'border-red': missingFields.includes('country_id')}">
<select class="detail" name="country_id" t-att-class="{'border-red': missingFields.includes('country_id')}" t-model="changes.country_id">
<option value="">None</option>
<t t-foreach="pos.countries" t-as="country" t-key="country.id">
<option t-att-value="country.id"
t-att-selected="props.partner.country_id ? ((country.id === props.partner.country_id[0]) ? true : undefined) : undefined">
<option t-att-value="country.id">
<t t-esc="country.name" />
</option>
</t>
@@ -71,12 +52,9 @@
<div class="partner-details-right">
<div class="partner-detail">
<span class="label">Language</span>
<select class="detail" name="lang"
t-on-change="captureChange"
t-att-class="{'border-red': missingFields.includes('lang')}">
<select class="detail" name="lang" t-att-class="{'border-red': missingFields.includes('lang')}" t-model="changes.lang">
<t t-foreach="pos.langs" t-as="lang" t-key="lang.id">
<option t-att-value="lang.code"
t-att-selected="props.partner.lang ? ((lang.code === props.partner.lang) ? true : undefined) : lang.code === pos.user.lang? true : undefined">
<option t-att-value="lang.code" t-att-selected="props.partner.lang ? ((lang.code === props.partner.lang) ? true : undefined) : lang.code === pos.user.lang? true : undefined">
<t t-esc="lang.name" />
</option>
</t>
@@ -84,46 +62,29 @@
</div>
<div class="partner-detail">
<span class="label">Email</span>
<input class="detail" name="email" type="email"
t-on-change="captureChange"
t-att-value="props.partner.email || ''"
t-att-class="{'border-red': missingFields.includes('email')}" />
<input class="detail" name="email" t-att-class="{'border-red': missingFields.includes('email')}" type="email" t-model="changes.email" />
</div>
<div class="partner-detail">
<span class="label">Phone</span>
<input class="detail" name="phone" type="tel"
t-on-change="captureChange"
t-att-value="props.partner.phone || ''"
t-att-class="{'border-red': missingFields.includes('phone')}" />
<input class="detail" name="phone" t-att-class="{'border-red': missingFields.includes('phone')}" type="tel" t-model="changes.phone" />
</div>
<div class="partner-detail">
<span class="label">Mobile</span>
<input class="detail" name="mobile" type="tel"
t-on-change="captureChange"
t-att-value="props.partner.mobile || ''"
t-att-class="{'border-red': missingFields.includes('tel')}" />
<input class="detail" name="mobile" t-att-class="{'border-red': missingFields.includes('mobile')}" type="tel" t-model="changes.mobile" />
</div>
<div class="partner-detail">
<span class="label">Barcode</span>
<input class="detail barcode" name="barcode" t-on-change="captureChange"
t-att-value="props.partner.barcode || ''"
t-att-class="{'border-red': missingFields.includes('barcode')}" />
<input class="detail barcode" name="barcode" t-model="changes.barcode" t-att-class="{'border-red': missingFields.includes('barcode')}" />
</div>
<div class="partner-detail">
<span class="label">Tax ID</span>
<input class="detail vat" name="vat" t-on-change="captureChange"
t-att-value="props.partner.vat || ''"
t-att-class="{'border-red': missingFields.includes('vat')}" />
<input class="detail vat" name="vat" t-model="changes.vat" t-att-class="{'border-red': missingFields.includes('vat')}" />
</div>
<div t-if="pos.pricelists.length gt 1" class="partner-detail">
<span class="label">Pricelist</span>
<select class="detail" name="property_product_pricelist"
t-on-change="captureChange"
t-att-class="{'border-red': missingFields.includes('property_product_pricelist')}">
<t t-foreach="pos.pricelists" t-as="pricelist"
t-key="pricelist.id">
<option t-att-value="pricelist.id"
t-att-selected="props.partner.property_product_pricelist ? (pricelist.id === props.partner.property_product_pricelist[0] ? true : undefined) : pricelist.id === pos.default_pricelist.id ? true : undefined">
<select class="detail" name="property_product_pricelist" t-att-class="{'border-red': missingFields.includes('property_product_pricelist')}" t-model="changes.property_product_pricelist">
<t t-foreach="pos.pricelists" t-as="pricelist" t-key="pricelist.id">
<option t-att-value="pricelist.id" t-att-selected="props.partner.property_product_pricelist ? (pricelist.id === props.partner.property_product_pricelist[0] ? true : undefined) : pricelist.id === pos.default_pricelist.id ? true : undefined">
<t t-esc="pricelist.display_name" />
</option>
</t>
@@ -133,5 +94,4 @@
</div>
</section>
</t>
</templates>