Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Change1:

...

New Backend API to fetch address hierarchy for a selected node :

Expand
titleapi/CustomerPortal/fetchGeneralAddressCode
Code Block
const fetchGeneralAddressCode = async (organisationId, extendedModels, params, options = {}) => {

    const { searchType, queryString } = params;
    ** searchType is hardcoded at frontend and sent in the body**
    if (!searchType) {
        throw helper.wrongInputError('Location Key should be present');
    }

    const metaData = await addressHierarchy.getAddressMetadata(extendedModels, organisationId);
    if (!metaData) {
        throw helper.wrongInputError('Metadata not found');
    }
    const hierarchy = metaData.hierarchy || [];
    const nodeHierarchyMetaData = hierarchy.find(obj => helper.sanitizeStringCode(obj.location_key) === helper.sanitizeStringCode(searchType));
    if (!nodeHierarchyMetaData) {
        throw helper.wrongInputError('Invalid Location Key');
    }
    const parentMetaDataArr = [];
    let searchTypeNodeFound = false;
    if (Array.isArray(hierarchy) && hierarchy.length > 0) {
        for (let i = 0; i < (hierarchy.length - 1); i++) {
            if (searchTypeNodeFound || helper.sanitizeStringCode(hierarchy[i].location_key) === helper.sanitizeStringCode(searchType)) {
                searchTypeNodeFound = true;
                parentMetaDataArr.push(hierarchy[i + 1]);
            }
        }
    }

    const queryParameters = [];
    let heirarchyId = nodeHierarchyMetaData.id;
    let nodeCode = helper.sanitizeStringCode(queryString);

    const toRet = {};
    toRet.data = {};
    queryParameters.push(organisationId);
    queryParameters.push(heirarchyId);
    queryParameters.push(nodeCode);

    const selectPart = 'SELECT addressNode.id as node_id,addressNode.code as node_code,addressNode.name as node_name,addressNode.hierarchy_id as hierarchy_id';
    const fromPart = ' FROM addressNode';
    let extendedSelectPart = '';
    let joinPart = '';
    const wherePart = ' WHERE addressNode.organisation_id = $1 and addressNode.hierarchy_id = $2 and addressNode.code = $3';

    if (parentMetaDataArr.length) {
        extendedSelectPart = ',addressNode.parent_node_id as parent_node_id,parentAddressNode.code as parent_code,parentAddressNode.name as parent_name';
        joinPart = ' JOIN addressNode AS parentAddressNode on addressNode.parent_node_id = parentAddressNode.id';
    }
    const queryToExecute = selectPart + extendedSelectPart + fromPart + joinPart + wherePart;
    const result = await helper.executeQueryAsync(extendedModels.AddressNode, queryToExecute, queryParameters, options);

    if (result[0] && result[0].node_name) {
        toRet.data[searchType] = result[0].node_name;
    } else {
        throw helper.wrongInputError('Data not found for this location key');
    }

    let nextNodesResult = result;
    for (let i = 0; i < parentMetaDataArr.length; i++) {
        try {
            heirarchyId = parentMetaDataArr[i].id;
            nodeCode = nextNodesResult[0].parent_code;
            const locationKey = parentMetaDataArr[i].location_key;

            queryParameters[1] = heirarchyId;
            queryParameters[2] = nodeCode;
            if (i === (parentMetaDataArr.length - 1)) {
                const lastNodeQueryResult = selectPart + fromPart + wherePart;
                nextNodesResult = await helper.executeQueryAsync(extendedModels.AddressNode, lastNodeQueryResult, queryParameters, options);
            } else {
                nextNodesResult = await helper.executeQueryAsync(extendedModels.AddressNode, queryToExecute, queryParameters, options);
            }
            if (nextNodesResult[0] && nextNodesResult[0].node_name) {
                toRet.data[locationKey] = nextNodesResult[0].node_name;
            }
        } catch (err) {
            //do nothing
        }
    }
    return toRet;
};
Code Block
//success response

...


{
    "data": {
        "pincode": "DAM",
        "city": "Z1",
        "state": "DAMMAM"
    }
}

...

Point to consider for better efficiency of this api(lesser load on db):

  1. on selecting any of value from dropdown of any field this api will be called by passing queryString=selectedValue of node and searchType=type of selected field

  2. What to do if any of middle node in hierarchy data is not found commented in above code snippet in catch block because it will hinder fetching data of other higher level (is it possible to occur in prod DB)

  3. Should disable all higher level nodes irrespective of the case that their value is fetched or not OR disable only those fields for which value is fetched. @saarthak

Change2:

Expand
titleApi's Body:
Code Block
{
    "searchType": "pincode",
    "queryString": "DAM"
}

Changes in filter for address node search:

  1. Instead of showing only Name of options in dropdown it will now be shown as Name (Code).
    This will require another update in find query in common/models/customer-portal-parts/address-node-api-helper.jsgetAddressNodeData

...

  1. Current filter

...

Current filter logic:

Expand
titleCurrent filter logic:
Code Block
const conditions = {
            hierarchy_id: hierarchyId,
            organisation_id: organisationId
        };

        if (isDataSearchApplied) {
            searchQuery = helper.sanitizeStringCode(searchQuery);
            let queryString = `${searchQuery}%`;

			if (sortResult) {
				queryString = `%${queryString}`;
			}

            conditions.code = {ilike: queryString};
			if (shouldSearchByName) {
				conditions.name = {ilike: queryString};
				delete conditions.code;
			}
        }
		
		const heirarchyData = await extendedModels.AddressNode.find({
			where: conditions,
			fields: { code: true, name: true, id: true },
			order: sortResult && 'name ASC',
		});
		

Updated filter logic:

Expand
titleUpdated filter logic:
Code Block
const conditions = {
			and: [
				{hierarchy_id: hierarchyId},
				{organisation_id: organisationId},
			],
		};

		if (isDataSearchApplied) {
            searchQuery = helper.sanitizeStringCode(searchQuery);
            let queryString = `${searchQuery}%`;

			if (sortResult) {
				queryString = `%${queryString}`;
			}

			const orConditions = shouldIncludeNameInSearchFilter
				? [
					{ name: { ilike: queryString } },
					{ code: { ilike: queryString } }
				]
				: 
				[{ code: { ilike: queryString } }];

			conditions.and.push({ or: orConditions });
        }
		
		const heirarchyData = await extendedModels.AddressNode.find({
			where: conditions,
			fields: { code: true, name: true, id: true }
		});
		

Frontend changes:

  1. At frontend side changes are to be done at 3 places

    1. Add Consignment modal (src/components/pages/details/AddDetails.tsx )

    2. Counter Booking → Add Address ( src/components/pages/OpsDashboard/Manage/counterBooking/AddAddress2.tsx )

    3. Customer-Portal → src/components/address/create-address.tsx

  2. List to be populated in dropdown will be fetched after entering atleast 3 characters in the input field for corresponding node( pincode, city, state, country).

  3. Debouncing will be implemented at all places whereever we are fetching list to be populated in the dropdown.

  4. In case allowOverrideHierarchy is false , we will let user only enter values from dropdown only.

  5. In case allowOverrideHierarchy is true: user can either select from dropdown or enter other values too.

  6. Only those fields will be disabled for which data is fetched, only when allow override hierarchy is false

Instead of showing only Name of options in dropdown it will now be shown as Name (Code). This will require another update in find query in common/models/customer-portal-parts/address-node-api-helper.jsgetAddressNodeData (mentioned above in backend changes).

Customer Portal changes

Changes in src/components/address/create-address.tsx

General Function to be called whenever any option from rendered dropdown is selected, it make call to api api/CustomerPortal/fetchGeneralAddressCode

Code Block
// this logic will be used to get whether isAddressHierarchyPresent is true or false
 if (Array.isArray(response.data.hierarchy) && response.data.hierarchy.length) {
                setIsAddressHierarchyPresent(true);
            }
  1. In case isAddressHierarchyPresent is false then we will render only Simple Input text field.

  2. If isAddressHierarchyPresent is true:

  • It fetches data from AddressNode table on basis of address hierarchy setup, and returns data for current and higher nodes to populate.

  • It also handle the logic to either disbale the higher level nodes based on whether their value is fetched and allow_override_hierarchy is false.

  • It also update (auto populate) the value of higher nodes

...

titlesaveAddressUsingAddressHierarchy code snippet called on onChange

...

  1. is applicable only on

...

  1. code or name, changes to add both code and name search
    common/models/customer-portal-parts/address-node-api-helper.jsgetAddressNodeData

Current filter logic:

Code Block
conditions.code = {ilike: queryString};
			if (shouldSearchByName) {
				conditions.name = {ilike: queryString};
				delete conditions.code;
			}

Updated filter logic:

Code Block
if (shouldSearchByBothNameAndCode) {
				condition.and.push({
					or: [
						{ name: { ilike: queryString } },
						{ code: { ilike: queryString } }
					]
				});
			} else {
				conditions.and.push({ code: { ilike: queryString } });
			}

Frontend changes:

  1. Changes are to be done at 3 places

    1. Add Consignment modal (src/components/pages/details/AddDetails.tsx )

    2. Counter Booking → Add Address ( src/components/pages/OpsDashboard/Manage/counterBooking/AddAddress2.tsx )

    3. Customer-Portalsrc/components/address/create-address.tsx

  2. List to be populated in dropdown will be fetched after entering atleast 3 characters in the input field for corresponding node( pincode, city, state, country).

  3. Debouncing will be implemented at all places wherever we are fetching list to be populated in the dropdown. (500ms)

  4. In case allowOverrideHierarchy is false , we will let user only enter values from dropdown only.

  5. In case allowOverrideHierarchy is true: user can either select from dropdown or enter other values too.

  6. Only those fields will be disabled for which data is fetched, only when allow override hierarchy is false

Customer Portal changes

  1. Changes in src/components/address/create-address.tsx

General Function to be called whenever any option from rendered dropdown is selected, it make call to api api/CustomerPortal/fetchGeneralAddressCode

Code Block
// this logic will be used to get whether isAddressHierarchyPresent is true or false
 if (Array.isArray(response.data.hierarchy) && response.data.hierarchy.length) {
                
nodeFound = true
setIsAddressHierarchyPresent(true);
            
parentLevelNodes.push({ key: hierarchyData[i].location_key, value: fetchLocality?.data[curNode] }); } } const fetchedAddressFieldsObj = parentLevelNodes?.reduce((acc, curr) => {
}
  1. In case isAddressHierarchyPresent is false then we will render only Simple Input text field.

  2. If isAddressHierarchyPresent is true:

  • It fetches data from AddressNode table on basis of address hierarchy setup, and returns data for current and higher nodes to populate.

  • It also handle the logic to either disbale the higher level nodes based on whether their value is fetched and allow_override_hierarchy is false.

  • It also update (auto populate) the value of higher nodes.

  • Drop down only on those fields which are in hierarchy

Expand
titlesaveAddressUsingAddressHierarchy code snippet called on onChange
Code Block
const saveAddressUsingAddressHierarchy = async (nodeValue: string, nodeType: string) => {
         if (!isKeyOfNewAddress(curr.keynodeType)) {|| !nodeValue) return;
              setLoading(true);

  acc[curr.key] = curr.value || null;  const fetchLocality = await fetchAddressUsingAddressHierarchy({ queryString: nodeValue, searchType: nodeType });

        if (curr!fetchLocality.key !== nodeType && (!isAddressMappingAllowed && curr.value)) {isSuccess) {
            setNewAddress({
                 setDisableLocality(true, curr.key);...newAddress,
                city: '',
    } else {          state: '',
             setDisableLocality(false, curr.key);  country: '',
            });
    }        setDisableLocality(false);
        } else {
            let nodeFound return= accfalse;
            },const {} as {parentLevelNodes = [key: string]:;
string | null });         for (let i = setNewAddress({0; i < hierarchyData?.length; i += 1) {
         ...newAddress,       const curNode = hierarchyData[i].location_key.toLowerCase();
      ...fetchedAddressFieldsObj,          if ((nodeFound || });
        }curNode === nodeType.toLocaleLowerCase()) && curNode in fetchLocality?.data) {
           setLoading(false);     };

Function to be called on typing text to be searched in address fields( pincode, city, state country)

Expand
titleSearching function logic called on onSearch
Code Block
const loadPincodes = async (value: any) nodeFound => {
 true;
      if (value?.length < 3) return;         if (!hierarchyData?.some((node: any) => nodeparentLevelNodes.push({ key: hierarchyData[i].location_key === 'pincode')) return, value: fetchLocality?.data[curNode] });
        setLoading(true);        }
 const response = await fetchPincodeList(value);       }
 if (response && response.isSuccess && Array.isArray(response?.data)) {     const fetchedAddressFieldsObj = parentLevelNodes?.reduce((acc, curr)    setLocalityPincodesList(response.data || []);=> {
        }        if setLoading(false);
(isKeyOfNewAddress(curr.key)) {
   };      const loadCitiesList = async (value: any) => {    acc[curr.key]     if (value?.length < 3) return;= curr.value || null;
         if (!hierarchyData?.some((node: any) => node.location_key === 'city')) return;    if (curr.key !== nodeType && setLoading(true);

 !isAddressMappingAllowed && curr.value)) {
      const response = await fetchCitiesList(value);         if (response && response.isSuccess && Array.isArray(response?.data)) {setDisableLocality(true, curr.key);
             setLocalityCitiesList(response.data || []);     } else {
 }         setLoading(false);     };      const loadStatesList = async (value: any) => {setDisableLocality(false, curr.key);
            if (value?.length < 3) return;    }
    if (!hierarchyData?.some((node: any) => node.location_key === 'state')) return;     }
   setLoading(true);          const response = await fetchStatesList(value)return acc;
         if (response && response.isSuccess && Array.isArray(response?.data)) {
    }, {} as { [key: string]: string | null });
        setLocalityStatesList(response.data || []);  setNewAddress({
      }          setLoading(false);...newAddress,
    };      const loadCountriesList = async (value: any) => { ...fetchedAddressFieldsObj,
         if (value?.length < 3}) return;
        if (!hierarchyData?.some((node: any) => node.location_key === 'country')) return;
  }

        setLoading(false);
     setLoading(true);

        const response = await fetchCountriesList(value);
};

Function to be called on typing text to be searched in address fields( pincode, city, state country)

Expand
titleSearching function logic called on onSearch
Code Block
const loadPincodes = async (value: any) => {
        if (response && response.isSuccess && Array.isArray(response?.data)) {value?.length < 3) return;
           if setCountriesList(response.data || []);
        }!hierarchyData?.some((node: any) => node.location_key === 'pincode')) return;
        setLoading(falsetrue);

   };

Debouncing logic is also applied :

Expand
titleCode snippet
Code Block
 useEffect(() => {  const response = await    loadPincodes(newAddress.pincodefetchPincodeList(value);
      }, [debouncePincode]); if (response && response.isSuccess && useEffect(() =>Array.isArray(response?.data)) {
        loadCitiesList(newAddress.city);     },setLocalityPincodesList(response.data || [debounceCity]);
     useEffect(() => {   }
        loadStatesListsetLoading(newAddress.statefalse);
    }, [debounceState]);

    ;

Debouncing logic is also applied :

Expand
titleCode snippet
Code Block
 useEffect(() => {
        loadCountriesListloadPincodes(newAddress.countrypincode);
    }, [debounceCountrydebouncePincode]);

//import { useDebounce } from 'hooks/use-debounce'; using already imported (was being used for w3wCode) useDebounce HoC
//SEARCH_TIMEOUT = 500 -> debounce time is used by default
const debouncePincode = useDebounce(newAddress.pincode);
const debounceCity = useDebounce(newAddress.city);
const debounceState = useDebounce(newAddress.state);
const debounceCountry = 500 -> debounce time is used by default
const debouncePincode = useDebounce(newAddress.countrypincode);

Following rendering logic is used for all nodes (pincode, state, city, country)

...

Expand
titleRendering snippet for address fields
Code Block
    const renderPincodeBox = () => {
        if (isAddressHierarchyPresent && isAddressMappingAllowed && hierarchyFieldsArr.include('pincode')) {
            return (
                <AutoComplete
                    value={newAddress.pincode}
                    className={classes.pincodeInput}
                    dropdownClassName={classes.pincodeInputDropdown}
                    options={
                        localityPincodesList.map((pincode: any) => {
                            return {
                                label: `${pincode.name} (${pincode.code})`,
                                value: pincode.code,
                            };
                        })
                    }
                    onSearch={loadPincodes}
                    onSelect={(e) => saveAddressUsingAddressHierarchy(e, 'pincode')}
                    onChange={(value) => updateAddressFieldWithValidations('pincode', value)}
                    style={{ textAlign: 'left' }}
                    allowClear
                >
                    <Input
                        className={classes.autoCompleteInputBox}
                        allowClear
                        placeholder={t('address_pincode')}
                    />
                </AutoComplete>
            );
        }
       else if (isAddressHierarchyPresent && !isAddressMappingAllowed && hierarchyFieldsArr.include('pincode')) {
            return (
                <Form.Item
                    className={classes.pincodeWithExtra}
                    name="pincode"
                    extra={samplePincode ? `Ex. ${samplePincode}` : undefined}
                >
                    <Select
                        value={newAddress.pincode}
                        dropdownClassName={classes.pincodeInputDropdown}
                        placeholder={t('address_pincode')}
                        onChange={(e) => {
                            updateAddressFieldWithValidations('pincode', e);
                            const isValid = isValidPincode(e);
                            if (isValid !== validPincode) {
                                setValidPincode(isValid);
                            }
                        }}
                        options={
                            localityPincodesList.map((pincode: any) => {
                                return {
                                    label: `${pincode.name} (${pincode.code})`,
                                    value: pincode.code,
                                };
                            })
                        }
                        onSelect={(e) => saveAddressUsingAddressHierarchy(e, 'pincode')}
                        defaultActiveFirstOption={false}
                        filterOption={false}
                        onSearch={loadPincodes}
                        showSearch
                        allowClear
                        style={validPincode ? {} : { borderColor: 'red' }}
                    />
                </Form.Item>
            );
        }Item>
        else {   );
        }
return (       return (
        <Input    <Input
                value={newAddress.pincode}
                    className={classes.pincodeInput}
  
                 placeholder={t('address_citypincode')}
                    onChange={(e) => updateAddressFieldWithValidations('pincodeInputpincode', e.target.value)}
  
             />
   
        );
        }
    };

Additional Details:

Changes in src/network/pickup.api.ts

...

Expand
titlechanges in src/network/api.constants.ts
Code Block
export const FETCH_STATES_LIST = '/api/CustomerPortal/addressManagement/getAddressNodeData?type=State';
export const FETCH_COUNTRIES_NODE_DATA = '/api/CustomerPortal/addressManagement/getAddressNodeData?type=Country';
export const FETCH_ADDRESS_FROM_ADDRESS_HIERARCHY = '/api/CustomerPortal/fetchGeneralAddressCode';

CRMDashboard changes:

General Function to be called whenever any option from rendered dropdown is selected, it make call to api api/CustomerPortal/fetchGeneralAddressCode

...

In case isAddressHierarchyPresent is false then we will render only Simple Input text field.

If isAddressHierarchyPresent is true:

...

It fetches data from AddressNode table on basis of address hierarchy setup, and returns data for current and higher nodes to populate.

...

It also handle the logic to either disbale the higher level nodes based on whether their value is fetched and allow_override_hierarchy is false.

...

'/api/CustomerPortal/fetchGeneralAddressCode';

CRMDashboard changes:

Similar logic as of Customer-Portal is used for rendering like on basis of isAddressHierarchyPresent and allowOverrideHierarchy.

Expand
titlesaveAddressUsingAddressHierarchy code snippet called on onChange
Code Block
 const saveAddressUsingAddressHierarchy = async (nodeValue, nodeType) => {
    if (!nodeType) return;

    if (!nodeValue) {
      handleAddressFields(nodeType, null, false);
      return;
    }

    const fetchLocality = await fetchAddressUsingAddressHierarchy({ queryString: nodeValue, searchType: nodeType });

    if (!fetchLocality.isSuccess) {
      form.setFieldsValue({
        cityName: '',
        stateName: '',
        countryName: '',
      });
      setDisableLocality(false, null);
    } else {
      handleAddressFields(nodeType, fetchLocality, false);
    }
  };

...