diff --git a/changelogs/unreleased/87894.json b/changelogs/unreleased/87894.json
new file mode 100644
index 0000000000..7a5a1f9aec
--- /dev/null
+++ b/changelogs/unreleased/87894.json
@@ -0,0 +1,6 @@
+{
+ "title": "Indicators: move logic to fetch available stock to card components",
+ "type": "refactor",
+ "packages": "stock",
+ "description": "There was a performance problem on the screens requiring the product indicators. To solve this slow performance problem, product indicators are now retrieved from the card component in the background. The old way of working retrieved the indicators for all the products in the list each time they were updated, before displaying them, which is rather cumbersome and shouldn't be used. The functions concerned have been removed. "
+}
diff --git a/packages/apps/stock/src/components/templates/product/ProductCard/ProductCard.tsx b/packages/apps/stock/src/components/templates/product/ProductCard/ProductCard.tsx
index bdfbccf98a..6d33c95913 100644
--- a/packages/apps/stock/src/components/templates/product/ProductCard/ProductCard.tsx
+++ b/packages/apps/stock/src/components/templates/product/ProductCard/ProductCard.tsx
@@ -16,31 +16,70 @@
* along with this program. If not, see .
*/
-import React from 'react';
+import React, {useEffect, useRef, useState} from 'react';
import {StyleSheet} from 'react-native';
import {ObjectCard, useThemeColor} from '@axelor/aos-mobile-ui';
-import {useMetafileUri, useTranslator} from '@axelor/aos-mobile-core';
+import {
+ useMetafileUri,
+ useSelector,
+ useTranslator,
+} from '@axelor/aos-mobile-core';
+import {getProductStockIndicators} from '../../../../api';
interface ProductCardProps {
style?: any;
+ productId: number;
+ productVersion: number;
name: string;
code: string;
picture: any;
- availableStock: number | null | undefined;
onPress: () => void;
}
const ProductCard = ({
style,
+ productId,
+ productVersion,
name,
code,
picture,
- availableStock,
onPress,
}: ProductCardProps) => {
const Colors = useThemeColor();
const I18n = useTranslator();
const formatMetaFile = useMetafileUri();
+ const isMounted = useRef(true);
+
+ const {activeCompany} = useSelector(state => state.user.user);
+
+ const [availableStock, setAvailableStock] = useState(null);
+
+ useEffect(() => {
+ isMounted.current = true;
+
+ if (productId != null) {
+ getProductStockIndicators({
+ productId: productId,
+ version: productVersion,
+ companyId: activeCompany?.id,
+ stockLocationId: null,
+ })
+ .then((res: any) => {
+ if (isMounted.current) {
+ setAvailableStock(res?.data?.object?.availableStock);
+ }
+ })
+ .catch(() => {
+ if (isMounted.current) {
+ setAvailableStock(null);
+ }
+ });
+ }
+
+ return () => {
+ isMounted.current = false;
+ };
+ }, [activeCompany?.id, productId, productVersion]);
return (
.
*/
-import React, {useCallback} from 'react';
+import React, {useCallback, useEffect, useRef, useState} from 'react';
import {StyleSheet} from 'react-native';
import {ObjectCard, useDigitFormat, useThemeColor} from '@axelor/aos-mobile-ui';
import {
@@ -25,6 +25,10 @@ import {
useTypeHelpers,
useTypes,
} from '@axelor/aos-mobile-core';
+import {
+ getProductStockIndicators,
+ fetchVariantAttributes,
+} from '../../../../api';
interface ProductAttribut {
attrName: string;
@@ -37,9 +41,10 @@ interface ProductVariantCardProps {
style?: any;
name: string;
code: string;
- attributesList: {attributes: ProductAttribut[]};
+ productId: number;
+ productVersion: number;
+ availabiltyData: {stockLocationId: number; companyId: number};
picture?: any;
- stockAvailability: number;
onPress: () => void;
}
@@ -47,9 +52,10 @@ const ProductVariantCard = ({
style,
name,
code,
- attributesList,
+ productId,
+ productVersion,
+ availabiltyData,
picture,
- stockAvailability,
onPress,
}: ProductVariantCardProps) => {
const Colors = useThemeColor();
@@ -58,16 +64,69 @@ const ProductVariantCard = ({
const formatNumber = useDigitFormat();
const {ProductVariantValue} = useTypes();
const {getItemTitle} = useTypeHelpers();
+ const isMounted = useRef(true);
+
+ const [attributes, setAttributesList] = useState<
+ ProductAttribut[] | undefined
+ >();
+ const [availableStock, setAvailableStock] = useState(null);
+
+ useEffect(() => {
+ isMounted.current = true;
+
+ return () => {
+ isMounted.current = false;
+ };
+ }, []);
+
+ useEffect(() => {
+ if (productId != null) {
+ fetchVariantAttributes({
+ productVariantId: productId,
+ version: productVersion,
+ })
+ .then((res: any) => {
+ if (isMounted.current) {
+ setAttributesList(res?.data?.object?.attributes);
+ }
+ })
+ .catch(() => {
+ if (isMounted.current) {
+ setAttributesList(null);
+ }
+ });
+ }
+ }, [availabiltyData, productId, productVersion]);
+
+ useEffect(() => {
+ if (productId != null) {
+ getProductStockIndicators({
+ productId: productId,
+ version: productVersion,
+ ...availabiltyData,
+ })
+ .then((res: any) => {
+ if (isMounted.current) {
+ setAvailableStock(res?.data?.object?.availableStock);
+ }
+ })
+ .catch(() => {
+ if (isMounted.current) {
+ setAvailableStock(null);
+ }
+ });
+ }
+ }, [availabiltyData, productId, productVersion]);
const renderAttrItems = useCallback(() => {
- if (!Array.isArray(attributesList?.attributes)) {
+ if (!Array.isArray(attributes)) {
return null;
}
let items = [];
- for (let index = 0; index < attributesList?.attributes.length; index++) {
- const attr = attributesList?.attributes[index];
+ for (let index = 0; index < attributes.length; index++) {
+ const attr = attributes[index];
if (attr != null) {
items.push({
@@ -88,7 +147,7 @@ const ProductVariantCard = ({
return items?.length > 0 ? {items} : null;
}, [
ProductVariantValue?.applicationPriceSelect,
- attributesList?.attributes,
+ attributes,
formatNumber,
getItemTitle,
]);
@@ -115,11 +174,10 @@ const ProductVariantCard = ({
items: [
{
displayText:
- stockAvailability > 0
+ availableStock > 0
? I18n.t('Stock_Available')
: I18n.t('Stock_Unavailable'),
- color:
- stockAvailability > 0 ? Colors.successColor : Colors.errorColor,
+ color: availableStock > 0 ? Colors.successColor : Colors.errorColor,
},
],
}}
diff --git a/packages/apps/stock/src/features/asyncFunctions-index.ts b/packages/apps/stock/src/features/asyncFunctions-index.ts
index 1925d28424..a20729545d 100644
--- a/packages/apps/stock/src/features/asyncFunctions-index.ts
+++ b/packages/apps/stock/src/features/asyncFunctions-index.ts
@@ -55,7 +55,6 @@ export {
} from './inventorySlice';
export {filterClients, filterSuppliers} from './partnerSlice';
export {
- fetchProductsAvailability,
fetchProductDistribution,
fetchProductIndicators,
} from './productIndicatorsSlice';
@@ -65,10 +64,7 @@ export {
updateProductLocker,
} from './productSlice';
export {searchProductTrackingNumber} from './productTrackingNumberSlice';
-export {
- fetchProductsAttributes,
- fetchProductVariants,
-} from './productVariantSlice';
+export {fetchProductVariants} from './productVariantSlice';
export {getRacks} from './racksListSlice';
export {fetchStockCorrectionReasons} from './stockCorrectionReasonSlice';
export {
diff --git a/packages/apps/stock/src/features/productIndicatorsSlice.js b/packages/apps/stock/src/features/productIndicatorsSlice.js
index 868f0f7304..30baa97a71 100644
--- a/packages/apps/stock/src/features/productIndicatorsSlice.js
+++ b/packages/apps/stock/src/features/productIndicatorsSlice.js
@@ -47,27 +47,6 @@ async function fetchData(data, {getState}) {
return await getProductAvailabilty(data, {getState});
}
-export const fetchProductsAvailability = createAsyncThunk(
- 'product/fetchProductsAvailability',
- async function (data, {getState}) {
- let promises = [];
- data.productList.forEach(product => {
- promises.push(
- fetchData(
- {
- productId: product.id,
- companyId: data.companyId,
- stockLocationId: data.stockLocationId,
- version: product.version,
- },
- {getState},
- ),
- );
- });
- return Promise.all(promises);
- },
-);
-
export const fetchProductDistribution = createAsyncThunk(
'product/fetchProductDistribution',
async function (data, {getState}) {
@@ -93,7 +72,6 @@ const initialState = {
loading: false,
loadingProductIndicators: false,
productIndicators: {},
- listAvailabilty: [],
listAvailabiltyDistribution: [],
};
@@ -108,13 +86,6 @@ const productIndicators = createSlice({
state.loadingProductIndicators = false;
state.productIndicators = action.payload;
});
- builder.addCase(fetchProductsAvailability.pending, state => {
- state.loading = true;
- });
- builder.addCase(fetchProductsAvailability.fulfilled, (state, action) => {
- state.loading = false;
- state.listAvailabilty = action.payload;
- });
builder.addCase(fetchProductDistribution.pending, state => {
state.loading = true;
});
diff --git a/packages/apps/stock/src/features/productVariantSlice.js b/packages/apps/stock/src/features/productVariantSlice.js
index ce2da13fa4..72f642bc64 100644
--- a/packages/apps/stock/src/features/productVariantSlice.js
+++ b/packages/apps/stock/src/features/productVariantSlice.js
@@ -21,7 +21,7 @@ import {
generateInifiniteScrollCases,
handlerApiCall,
} from '@axelor/aos-mobile-core';
-import {fetchVariantAttributes, fetchVariants} from '../api/product-api';
+import {fetchVariants} from '../api/product-api';
export const fetchProductVariants = createAsyncThunk(
'product/fetchProductVariant',
@@ -36,44 +36,11 @@ export const fetchProductVariants = createAsyncThunk(
},
);
-var getProductAttributes = async (data, {getState}) => {
- return handlerApiCall({
- fetchFunction: fetchVariantAttributes,
- data,
- action: 'Stock_SliceAction_FetchProductVariantAttributes',
- getState,
- responseOptions: {isArrayResponse: true},
- });
-};
-
-async function fetchData(data, {getState}) {
- return await getProductAttributes(data, {getState});
-}
-
-export const fetchProductsAttributes = createAsyncThunk(
- 'product/fetchProductsAttributes',
- async function (data, {getState}) {
- let promises = [];
- data.productList.forEach(product => {
- promises.push(
- fetchData(
- {productVariantId: product.id, version: product.version},
- {getState},
- ),
- );
- });
- return Promise.all(promises);
- },
-);
-
const initialState = {
loadingProductList: false,
moreLoading: false,
isListEnd: false,
productListVariables: [],
-
- loading: false,
- listProductsAttributes: [],
};
const productSlice = createSlice({
@@ -86,13 +53,6 @@ const productSlice = createSlice({
isListEnd: 'isListEnd',
list: 'productListVariables',
});
- builder.addCase(fetchProductsAttributes.pending, (state, action) => {
- state.loading = true;
- });
- builder.addCase(fetchProductsAttributes.fulfilled, (state, action) => {
- state.loading = false;
- state.listProductsAttributes = action.payload;
- });
},
});
diff --git a/packages/apps/stock/src/screens/products/ProductListScreen.js b/packages/apps/stock/src/screens/products/ProductListScreen.js
index c32587dc79..ee45e72819 100644
--- a/packages/apps/stock/src/screens/products/ProductListScreen.js
+++ b/packages/apps/stock/src/screens/products/ProductListScreen.js
@@ -16,27 +16,22 @@
* along with this program. If not, see .
*/
-import React, {useEffect, useState, useCallback} from 'react';
+import React, {useState, useCallback} from 'react';
import {Screen} from '@axelor/aos-mobile-ui';
import {
displayItemName,
SearchListView,
- useDispatch,
useSelector,
useTranslator,
} from '@axelor/aos-mobile-core';
import {ProductCard} from '../../components';
import {searchProducts} from '../../features/productSlice';
-import {fetchProductsAvailability} from '../../features/productIndicatorsSlice';
const productScanKey = 'product_product-list';
const ProductListScreen = ({navigation}) => {
const I18n = useTranslator();
- const dispatch = useDispatch();
- const {activeCompany} = useSelector(state => state.user.user);
- const {listAvailabilty} = useSelector(state => state.productIndicators);
const {loadingProduct, moreLoadingProduct, isListEndProduct, productList} =
useSelector(state => state.product);
@@ -52,18 +47,6 @@ const ProductListScreen = ({navigation}) => {
[navigation],
);
- useEffect(() => {
- if (productList != null) {
- dispatch(
- fetchProductsAvailability({
- productList: productList,
- companyId: activeCompany?.id,
- stockLocationId: null,
- }),
- );
- }
- }, [activeCompany, dispatch, productList]);
-
return (
{
searchNavigate={navigate}
scanKeySearch={productScanKey}
expandableFilter={false}
- renderListItem={({item, index}) => (
+ renderListItem={({item}) => (
showProductDetails(item)}
/>
)}
diff --git a/packages/apps/stock/src/screens/products/ProductListVariantScreen.js b/packages/apps/stock/src/screens/products/ProductListVariantScreen.js
index 7c350bdc57..b94a288bea 100644
--- a/packages/apps/stock/src/screens/products/ProductListVariantScreen.js
+++ b/packages/apps/stock/src/screens/products/ProductListVariantScreen.js
@@ -16,15 +16,11 @@
* along with this program. If not, see .
*/
-import React, {useCallback, useEffect, useMemo} from 'react';
+import React, {useCallback, useMemo} from 'react';
import {Screen, ScrollList} from '@axelor/aos-mobile-ui';
import {useDispatch, useSelector, useTranslator} from '@axelor/aos-mobile-core';
import {ProductVariantCard} from '../../components';
-import {
- fetchProductsAttributes,
- fetchProductVariants,
-} from '../../features/productVariantSlice';
-import {fetchProductsAvailability} from '../../features/productIndicatorsSlice';
+import {fetchProductVariants} from '../../features/productVariantSlice';
const ProductListVariantScreen = ({route, navigation}) => {
const product = route.params.product;
@@ -34,14 +30,8 @@ const ProductListVariantScreen = ({route, navigation}) => {
() => product?.parentProduct?.id,
[product?.parentProduct?.id],
);
- const {
- loadingProductList,
- moreLoading,
- isListEnd,
- productListVariables,
- listProductsAttributes,
- } = useSelector(state => state.productVariant);
- const {listAvailabilty} = useSelector(state => state.productIndicators);
+ const {loadingProductList, moreLoading, isListEnd, productListVariables} =
+ useSelector(state => state.productVariant);
const I18n = useTranslator();
const dispatch = useDispatch();
@@ -59,19 +49,6 @@ const ProductListVariantScreen = ({route, navigation}) => {
[dispatch, parentProductId],
);
- useEffect(() => {
- if (productListVariables != null) {
- dispatch(
- fetchProductsAvailability({
- productList: productListVariables,
- companyId: companyID,
- stockLocationId: stockLocationId,
- }),
- );
- dispatch(fetchProductsAttributes({productList: productListVariables}));
- }
- }, [companyID, dispatch, productListVariables, stockLocationId]);
-
const navigateToProductVariable = productVar => {
navigation.navigate('ProductStockDetailsScreen', {product: productVar});
};
@@ -81,18 +58,15 @@ const ProductListVariantScreen = ({route, navigation}) => {
(
+ renderItem={({item}) => (
navigateToProductVariable(item)}
/>
)}