[IMP] l10n_in: add the hsn autocomplete widget
- In this commit, we make some requests in js to get a list of HSN codes to match with the input entered by the user in the field. - And for the options attribute in hsn_autocomplete widget, when a user selects an option from the list, the hsn_decription will be stored in the field specified by the user. task-3646598 closes odoo/odoo#151507 Signed-off-by: Quentin Lejeune (qle) <qle@odoo.com>
This commit is contained in:
@@ -50,4 +50,9 @@ Sheet, now only Vertical format has been permitted Which is Supported By Odoo.
|
||||
'demo/product_demo.xml',
|
||||
],
|
||||
'license': 'LGPL-3',
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'l10n_in/static/src/components/**/*',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { AutoComplete } from "@web/core/autocomplete/autocomplete";
|
||||
import { useChildRef } from "@web/core/utils/hooks";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { CharField, charField } from "@web/views/fields/char/char_field";
|
||||
import { useInputField } from "@web/views/fields/input_field_hook";
|
||||
|
||||
const l10N_IN_HSN_SERVICE_URL = "https://services.gst.gov.in/commonservices/hsn/search/qsearch";
|
||||
|
||||
export class L10nInHsnAutoComplete extends CharField {
|
||||
static template = "l10n_in.hsnAutoComplete";
|
||||
static components = {
|
||||
...CharField.components,
|
||||
AutoComplete,
|
||||
};
|
||||
static props = {
|
||||
...CharField.props,
|
||||
l10nInHsnDescription: { type: String, optional: true },
|
||||
};
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
this.inputRef = useChildRef();
|
||||
useInputField({
|
||||
getValue: () => this.props.record.data[this.props.name] || "",
|
||||
parse: (v) => this.parse(v),
|
||||
ref: this.inputRef,
|
||||
});
|
||||
}
|
||||
|
||||
async getHsnSuggestions(value) {
|
||||
const suggestions = [];
|
||||
const onlyDigits = !isNaN(value) && value.indexOf(" ") < 0;
|
||||
const params = [
|
||||
{ type: "byCode", category: "null" }, // For code
|
||||
{ type: "byDesc", category: "P" }, // For products
|
||||
{ type: "byDesc", category: "S" }, // For services
|
||||
];
|
||||
const filteredParams = onlyDigits ? [params[0]] : params.slice(1);
|
||||
try {
|
||||
await Promise.all(
|
||||
filteredParams.map(async (param) => {
|
||||
const controller = new AbortController();
|
||||
const signal = controller.signal;
|
||||
setTimeout(() => controller.abort(), 5000);
|
||||
const res = await fetch(
|
||||
`${l10N_IN_HSN_SERVICE_URL}?inputText=${value}&selectedType=${param.type}&category=${param.category}`,
|
||||
{ signal }
|
||||
);
|
||||
if (!res.ok) {
|
||||
throw new Error(res.statusText);
|
||||
}
|
||||
const resData = await res.json();
|
||||
if (resData.data) {
|
||||
suggestions.push(
|
||||
...resData.data
|
||||
.filter((item) => item.c.length > 3)
|
||||
.map((item) => ({
|
||||
label: item.c,
|
||||
description: item.n,
|
||||
}))
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
suggestions.push({
|
||||
label: _t("Could not contact API"),
|
||||
unselectable: false,
|
||||
});
|
||||
console.warn("HSN Autocomplete API error:", e);
|
||||
}
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
get sources() {
|
||||
return [
|
||||
{
|
||||
options: async (request) => {
|
||||
if (request?.length > 2) {
|
||||
return await this.getHsnSuggestions(request);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
optionTemplate: "hsn_autocomplete.DropdownOption",
|
||||
placeholder: _t("Searching..."),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
onSelect(option) {
|
||||
const data = { [this.props.name]: option.label };
|
||||
if (this.props.l10nInHsnDescription) {
|
||||
data[this.props.l10nInHsnDescription] = option.description;
|
||||
}
|
||||
this.props.record.update(data);
|
||||
}
|
||||
}
|
||||
|
||||
export const l10nInHsnAutoComplete = {
|
||||
...charField,
|
||||
component: L10nInHsnAutoComplete,
|
||||
supportedOptions: [
|
||||
{
|
||||
label: _t("hsn description field"),
|
||||
name: "hsn_description_field",
|
||||
type: "string",
|
||||
},
|
||||
],
|
||||
extractProps: ({ options }) => ({
|
||||
l10nInHsnDescription: options.hsn_description_field,
|
||||
}),
|
||||
};
|
||||
|
||||
registry.category("fields").add("l10n_in_hsn_autocomplete", l10nInHsnAutoComplete);
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<templates>
|
||||
<t t-name="l10n_in.hsnAutoComplete">
|
||||
<AutoComplete
|
||||
value="props.record.data[props.name] || ''"
|
||||
sources="sources"
|
||||
onSelect.bind="onSelect"
|
||||
input="inputRef"
|
||||
placeholder="props.placeholder || ''"
|
||||
/>
|
||||
</t>
|
||||
|
||||
<t t-name="hsn_autocomplete.DropdownOption">
|
||||
<div class="text-wrap">
|
||||
<strong t-out="option.label"/>
|
||||
<div t-out="option.description"/>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -7,8 +7,8 @@
|
||||
<field name="inherit_id" ref="product.product_template_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="categ_id" position="after">
|
||||
<field name="l10n_in_hsn_code" invisible="'IN' not in fiscal_country_codes"/>
|
||||
<field name="l10n_in_hsn_description" invisible="'IN' not in fiscal_country_codes"/>
|
||||
<field name="l10n_in_hsn_code" widget="l10n_in_hsn_autocomplete" invisible="'IN' not in fiscal_country_codes"/>
|
||||
<field name="l10n_in_hsn_description" invisible="1"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
Reference in New Issue
Block a user