[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:
+22
-10
@@ -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"),
|
||||
|
||||
+20
-60
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user