diff --git a/.env.example b/.env.example index 21c07831..a6d915d4 100644 --- a/.env.example +++ b/.env.example @@ -4,7 +4,7 @@ VUE_APP_CACHE_MAX_AGE=3600 VUE_APP_VIEW_SIZE=10 VUE_APP_PERMISSION_ID="INVCOUNT_APP_VIEW" VUE_APP_DEFAULT_LOG_LEVEL="error" -VUE_APP_PRDT_IDENT=["productId", "groupId", "groupName", "internalName", "parentProductName", "sku", "title", "SHOPIFY_PROD_SKU", "ERP_ID", "UPCA"] +VUE_APP_PRDT_IDENT=["productId", "groupId", "groupName", "internalName", "parentProductName", "primaryProductCategoryName", "title"] VUE_APP_MAPPING_TYPES={"INVCOUNT": "INVCNT_MAPPING_PREF"} VUE_APP_MAPPING_INVCOUNT={"countImportName": { "label": "Count name", "required": true}, "productSku": { "label": "Product SKU", "required": true, "description": "Products will not be deduplicated. Make sure products are only added to a count once."}, "facility": { "label": "Facility", "required": false, "description": "If a file includes multiple facilities, a count is created for every facility. All items with no facility location will be added to the same count." }, "statusId": { "label": "Status", "required": false, "description": "Defaults to 'Draft'" }, "dueDate": { "label": "Due date", "description": "Format: yyyy-mm-dd", "required": false }} VUE_APP_LOGIN_URL="http://launchpad.hotwax.io/login" \ No newline at end of file diff --git a/src/authorization/Rules.ts b/src/authorization/Rules.ts index 12f21bb1..f2760238 100644 --- a/src/authorization/Rules.ts +++ b/src/authorization/Rules.ts @@ -6,6 +6,6 @@ export default { "APP_STORE_PERMISSIONS_VIEW": "COMMON_ADMIN", "APP_SETTINGS_VIEW": "", "APP_COUNT_VIEW": "FULFILL_INVCUNT_ADMIN OR INV_COUNT_ADMIN", - "APP_PRODUCT_IDENTIFIER_UPDATE": "", + "APP_PRODUCT_IDENTIFIER_UPDATE": "COMMON_ADMIN", "INVCOUNT_APP_VIEW": "INVCOUNT_APP_VIEW" } as any \ No newline at end of file diff --git a/src/components/AddProductModal.vue b/src/components/AddProductModal.vue index ef4c5735..a169dc4a 100644 --- a/src/components/AddProductModal.vue +++ b/src/components/AddProductModal.vue @@ -19,7 +19,7 @@ <Image :src="product.mainImageUrl" /> </ion-thumbnail> <ion-label> - <h2>{{ getProductIdentificationValue(productStoreSettings["productIdentificationPref"].primaryId, product) }}</h2> + <h2>{{ getProductIdentificationValue(productStoreSettings["productIdentificationPref"].primaryId, product) || getProduct(product.productId).productName }}</h2> <p>{{ getProductIdentificationValue(productStoreSettings["productIdentificationPref"].secondaryId, product) }}</p> </ion-label> <ion-icon v-if="isProductAvailableInCycleCount(product.productId)" color="success" :icon="checkmarkCircle" /> @@ -73,6 +73,7 @@ const props = defineProps(["cycleCount"]) const products = computed(() => store.getters["product/getProducts"]) const isScrollable = computed(() => store.getters["product/isScrollable"]) const productStoreSettings = computed(() => store.getters["user/getProductStoreSettings"]) +const getProduct = computed(() => (id: any) => store.getters["product/getProduct"](id)) let queryString = ref('') const isSearching = ref(false); diff --git a/src/components/AssignedCountPopover.vue b/src/components/AssignedCountPopover.vue index 7080012e..49403f6c 100644 --- a/src/components/AssignedCountPopover.vue +++ b/src/components/AssignedCountPopover.vue @@ -1,7 +1,7 @@ <template> <ion-content> <ion-list> - <ion-list-header>{{ getProductIdentificationValue(productStoreSettings["productIdentificationPref"].primaryId, getProduct(item.productId)) || item.productId }}</ion-list-header> + <ion-list-header>{{ getProductIdentificationValue(productStoreSettings["productIdentificationPref"].primaryId, getProduct(item.productId)) || getProduct(item.productId).productName }}</ion-list-header> <ion-item :lines="item.quantity ? 'none' : 'full'"> <ion-label>{{ translate("Last counted")}}</ion-label> <ion-note slot="end">{{ timeFromNow(item.lastCountedDate) }}</ion-note> diff --git a/src/locales/en.json b/src/locales/en.json index 1e899e96..96e89208 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -26,7 +26,7 @@ "Before": "Before", "Browser TimeZone": "Browser TimeZone", "Browser time zone": "Browser time zone", - "Built: ": "Built: {builtDateTime}", + "Built:": "Built: {builtDateTime}", "Bulk Upload Cycle Counts": "Bulk Upload Cycle Counts", "Camera permission denied.": "Camera permission denied.", "Cancel": "Cancel", @@ -159,6 +159,7 @@ "NOT COUNTED": "NOT COUNTED", "not counted": "not counted", "No facility": "No facility", + "None": "None", "Fetching time zones": "Fetching time zones", "Ok": "Ok", "OMS": "OMS", @@ -176,9 +177,8 @@ "Please select the column that corresponds to the product identifier": "Please select the column that corresponds to the product identifier", "Preparing file to downlaod...": "Preparing file to downlaod...", "Primary": "Primary", - "Primary identifier": "Primary identifier", "Primary product ID": "Primary product ID", - "Primary Product Identifier": "Primary Product Identifier", + "primary identifier": "primary identifier", "processed": "processed", "processing": "processing", "Product Identifier": "Product Identifier", @@ -218,7 +218,6 @@ "Saving recount will replace the existing count for item.": "Saving recount will replace the existing count for item.", "Scan": "Scan", "Scan or search products": "Scan or search products", - "Secondary Product Identifier": "Secondary Product Identifier", "selected": "selected", "Select": "Select", "Select fields": "Select fields", @@ -230,6 +229,7 @@ "Search time zones": "Search time zones", "Searching on SKU": "Searching on SKU", "Secondary": "Secondary", + "secondary identifier": "secondary identifier", "Select all the required fields to continue": "Select all the required fields to continue", "Select date": "Select date", "Select the column containing products": "Select the column containing products", @@ -284,7 +284,7 @@ "variance": "variance", "Variance reason": "Variance reason", "Variance updated successfully": "Variance updated successfully", - "Version: ": "Version: {appVersion}", + "Version:": "Version: {appVersion}", "View": "View", "You do not have permission to access the app.": "You do not have permission to access the app.", "You do not have permission to access this page": "You do not have permission to access this page", diff --git a/src/services/UserService.ts b/src/services/UserService.ts index c70928d4..0ecf1f5b 100644 --- a/src/services/UserService.ts +++ b/src/services/UserService.ts @@ -249,12 +249,29 @@ const getFieldMappings = async (payload: any): Promise <any> => { }); } +const fetchGoodIdentificationTypes = async (payload: any): Promise <any> => { + const omsRedirectionInfo = store.getters["user/getOmsRedirectionInfo"] + const baseURL = omsRedirectionInfo.url.startsWith('http') ? omsRedirectionInfo.url.includes('/api') ? omsRedirectionInfo.url : `${omsRedirectionInfo.url}/api/` : `https://${omsRedirectionInfo.url}.hotwax.io/api/`; + + return await client({ + url: "performFind", + method: "post", + baseURL, + data: payload, + headers: { + "Authorization": 'Bearer ' + omsRedirectionInfo.token, + 'Content-Type': 'application/json' + } + }); +} + export const UserService = { createFieldMapping, createProductStoreSetting, deleteFieldMapping, fetchAssociatedFacilities, fetchFacilities, + fetchGoodIdentificationTypes, fetchProductStores, fetchProductStoreSettings, getAvailableTimeZones, diff --git a/src/store/modules/user/UserState.ts b/src/store/modules/user/UserState.ts index 1000ac45..35de61ea 100644 --- a/src/store/modules/user/UserState.ts +++ b/src/store/modules/user/UserState.ts @@ -25,5 +25,6 @@ export default interface UserState { primaryId: string, secondaryId: string } - } + }, + goodIdentificationTypes: Array<string>; } \ No newline at end of file diff --git a/src/store/modules/user/actions.ts b/src/store/modules/user/actions.ts index bd7567ac..45715d50 100644 --- a/src/store/modules/user/actions.ts +++ b/src/store/modules/user/actions.ts @@ -106,6 +106,7 @@ const actions: ActionTree<UserState, RootState> = { primaryId: 'productId', secondaryId: '' }}) + commit(types.USER_GOOD_IDENTIFICATION_TYPES_UPDATED, []) this.dispatch('count/clearCycleCounts') this.dispatch('count/clearCycleCountItems') @@ -545,6 +546,31 @@ const actions: ActionTree<UserState, RootState> = { name: '', value: {} }) + }, + + async fetchGoodIdentificationTypes({ commit }, parentTypeId = "HC_GOOD_ID_TYPE") { + let identificationTypes = process.env.VUE_APP_PRDT_IDENT ? JSON.parse(process.env.VUE_APP_PRDT_IDENT) : [] + const payload = { + "inputFields": { + parentTypeId + }, + "fieldList": ["goodIdentificationTypeId", "description"], + "viewSize": 50, + "entityName": "GoodIdentificationType", + } + try { + const resp = await UserService.fetchGoodIdentificationTypes(payload) + if (!hasError(resp) && resp.data?.docs?.length) { + const identificationOptions = resp.data.docs?.map((fetchedGoodIdentificationType: any) => fetchedGoodIdentificationType.goodIdentificationTypeId) || []; + // Merge the arrays and remove duplicates + identificationTypes = Array.from(new Set([...identificationOptions, ...identificationTypes])).sort(); + } else { + throw resp.data; + } + } catch (err) { + console.error('Failed to fetch the good identification types, setting default types') + } + commit(types.USER_GOOD_IDENTIFICATION_TYPES_UPDATED, identificationTypes) } } export default actions; \ No newline at end of file diff --git a/src/store/modules/user/getters.ts b/src/store/modules/user/getters.ts index 66a113c4..c1fcf271 100644 --- a/src/store/modules/user/getters.ts +++ b/src/store/modules/user/getters.ts @@ -49,6 +49,9 @@ const getters: GetterTree <UserState, RootState> = { return fieldMapping ? fieldMapping : {} } return state.fieldMappings; + }, + getGoodIdentificationTypes(state) { + return state.goodIdentificationTypes; } } export default getters; \ No newline at end of file diff --git a/src/store/modules/user/index.ts b/src/store/modules/user/index.ts index cd39ea09..94535e84 100644 --- a/src/store/modules/user/index.ts +++ b/src/store/modules/user/index.ts @@ -34,7 +34,8 @@ const userModule: Module<UserState, RootState> = { primaryId: 'productId', secondaryId: '' }, - } + }, + goodIdentificationTypes: [] }, getters, actions, diff --git a/src/store/modules/user/mutation-types.ts b/src/store/modules/user/mutation-types.ts index d2401776..a3b25a1d 100644 --- a/src/store/modules/user/mutation-types.ts +++ b/src/store/modules/user/mutation-types.ts @@ -12,4 +12,5 @@ export const USER_CURRENT_PRODUCT_STORE_UPDATED = SN_USER + '/CURRENT_PRODUCT_ST export const USER_PRODUCT_STORE_SETTING_UPDATED = SN_USER + '/PRODUCT_STORE_SETTING_UPDATED' export const USER_FIELD_MAPPINGS_UPDATED = SN_USER + '/FIELD_MAPPINGS_UPDATED' export const USER_FIELD_MAPPING_CREATED = SN_USER + '/FIELD_MAPPING_CREATED' -export const USER_CURRENT_FIELD_MAPPING_UPDATED = SN_USER + '/_CURRENT_FIELD_MAPPING_UPDATED' \ No newline at end of file +export const USER_CURRENT_FIELD_MAPPING_UPDATED = SN_USER + '/_CURRENT_FIELD_MAPPING_UPDATED' +export const USER_GOOD_IDENTIFICATION_TYPES_UPDATED = SN_USER + '/PRODUCT_IDENTIFICATION_OPTIONS_UPDATED' \ No newline at end of file diff --git a/src/store/modules/user/mutations.ts b/src/store/modules/user/mutations.ts index 755028e1..301d65c6 100644 --- a/src/store/modules/user/mutations.ts +++ b/src/store/modules/user/mutations.ts @@ -51,6 +51,9 @@ const mutations: MutationTree <UserState> = { name: payload.name, value: payload.value }; + }, + [types.USER_GOOD_IDENTIFICATION_TYPES_UPDATED](state, payload = []) { + state.goodIdentificationTypes = payload.length ? payload : process.env.VUE_APP_PRDT_IDENT ? JSON.parse(process.env.VUE_APP_PRDT_IDENT) : [] } } export default mutations; \ No newline at end of file diff --git a/src/views/AssignedDetail.vue b/src/views/AssignedDetail.vue index 2da7f240..1351bcb5 100644 --- a/src/views/AssignedDetail.vue +++ b/src/views/AssignedDetail.vue @@ -75,7 +75,7 @@ <Image :src="getProduct(item.productId).mainImageUrl"/> </ion-thumbnail> <ion-label> - <h2>{{ getProductIdentificationValue(productStoreSettings["productIdentificationPref"].primaryId, getProduct(item.productId)) }}</h2> + <h2>{{ getProductIdentificationValue(productStoreSettings["productIdentificationPref"].primaryId, getProduct(item.productId)) || getProduct(item.productId).productName }}</h2> <p>{{ getProductIdentificationValue(productStoreSettings["productIdentificationPref"].secondaryId, getProduct(item.productId)) }}</p> </ion-label> </ion-item> @@ -138,7 +138,7 @@ import { computed, defineProps, nextTick, ref } from "vue"; import { translate } from '@/i18n' import { addOutline, calendarClearOutline, businessOutline, personCircleOutline, ellipsisVerticalOutline, lockClosedOutline } from "ionicons/icons"; -import { IonBackButton, IonButton, IonButtons, IonChip, IonContent, IonDatetime, IonModal, IonFab, IonFabButton, IonHeader, IonIcon, IonInput, IonItem, IonLabel, IonList, IonPage, IonSpinner, IonThumbnail, IonTitle, IonToolbar, modalController, onIonViewWillEnter, popoverController, onIonViewWillLeave } from "@ionic/vue"; +import { IonBackButton, IonButton, IonButtons, IonChip, IonContent, IonDatetime, IonModal, IonFab, IonFabButton, IonHeader, IonIcon, IonInput, IonItem, IonLabel, IonList, IonPage, IonThumbnail, IonTitle, IonToolbar, modalController, onIonViewWillEnter, popoverController, onIonViewWillLeave } from "@ionic/vue"; import AssignedCountPopover from "@/components/AssignedCountPopover.vue" import store from "@/store" import logger from "@/logger" diff --git a/src/views/CountDetail.vue b/src/views/CountDetail.vue index e7ca1bbf..6c6efca2 100644 --- a/src/views/CountDetail.vue +++ b/src/views/CountDetail.vue @@ -70,7 +70,7 @@ <div class="detail" v-if="Object.keys(product)?.length"> <ion-item lines="none"> <ion-label class="ion-text-wrap"> - <h1>{{ getProductIdentificationValue(productStoreSettings["productIdentificationPref"].primaryId, getProduct(product.productId)) }}</h1> + <h1>{{ getProductIdentificationValue(productStoreSettings["productIdentificationPref"].primaryId, getProduct(product.productId)) || getProduct(product.productId).productName }}</h1> <p>{{ getProductIdentificationValue(productStoreSettings["productIdentificationPref"].secondaryId, getProduct(product.productId)) }}</p> </ion-label> diff --git a/src/views/DraftDetail.vue b/src/views/DraftDetail.vue index abd6ef4f..847bbce2 100644 --- a/src/views/DraftDetail.vue +++ b/src/views/DraftDetail.vue @@ -109,7 +109,7 @@ <Image :src="getProduct(item.productId).mainImageUrl"/> </ion-thumbnail> <ion-label class="ion-text-wrap"> - <h2>{{ getProductIdentificationValue(productStoreSettings["productIdentificationPref"].primaryId, getProduct(item.productId)) }}</h2> + <h2>{{ getProductIdentificationValue(productStoreSettings["productIdentificationPref"].primaryId, getProduct(item.productId)) || getProduct(item.productId).productName }}</h2> <p>{{ getProductIdentificationValue(productStoreSettings["productIdentificationPref"].secondaryId, getProduct(item.productId)) }}</p> </ion-label> </ion-item> diff --git a/src/views/PendingReviewDetail.vue b/src/views/PendingReviewDetail.vue index a1377242..d1574e67 100644 --- a/src/views/PendingReviewDetail.vue +++ b/src/views/PendingReviewDetail.vue @@ -104,7 +104,7 @@ <Image :src="getProduct(item.productId).mainImageUrl"/> </ion-thumbnail> <ion-label class="ion-text-wrap"> - <h2>{{ getProductIdentificationValue(productStoreSettings["productIdentificationPref"].primaryId, getProduct(item.productId)) }}</h2> + <h2>{{ getProductIdentificationValue(productStoreSettings["productIdentificationPref"].primaryId, getProduct(item.productId)) || getProduct(item.productId).productName }}</h2> <p>{{ getProductIdentificationValue(productStoreSettings["productIdentificationPref"].secondaryId, getProduct(item.productId)) }}</p> </ion-label> </ion-item> diff --git a/src/views/ProductItemList.vue b/src/views/ProductItemList.vue index b64250e7..513d186a 100644 --- a/src/views/ProductItemList.vue +++ b/src/views/ProductItemList.vue @@ -5,7 +5,7 @@ </ion-thumbnail> <ion-label class="ion-text-wrap"> <p class="overline">{{ item.itemStatusId === 'INV_COUNT_REJECTED' ? "rejected" : "" }}</p> - <h2>{{ getProductIdentificationValue(productStoreSettings["productIdentificationPref"].primaryId, getProduct(item.productId)) }}</h2> + <h2>{{ getProductIdentificationValue(productStoreSettings["productIdentificationPref"].primaryId, getProduct(item.productId)) || getProduct(item.productId).productName }}</h2> <p>{{ getProductIdentificationValue(productStoreSettings["productIdentificationPref"].secondaryId, getProduct(item.productId)) }}</p> </ion-label> <ion-badge slot="end" color="danger" v-if="item.itemStatusId === 'INV_COUNT_REJECTED'"> diff --git a/src/views/Settings.vue b/src/views/Settings.vue index e7b0a4a2..18a6e89a 100644 --- a/src/views/Settings.vue +++ b/src/views/Settings.vue @@ -121,12 +121,12 @@ </ion-card-content> <ion-item> - <ion-select :label="translate('Primary Product Identifier')" :disabled="!hasPermission(Actions.APP_PRODUCT_IDENTIFIER_UPDATE) || !(currentFacility?.productStore?.productStoreId || currentProductStore.productStoreId)" interface="popover" :placeholder="translate('primary identifier')" :value="productStoreSettings['productIdentificationPref'].primaryId" @ionChange="setProductIdentificationPref($event.detail.value, 'primaryId')"> + <ion-select :label="translate('Primary')" :disabled="!hasPermission(Actions.APP_PRODUCT_IDENTIFIER_UPDATE) || !(currentFacility?.productStore?.productStoreId || currentProductStore.productStoreId)" interface="popover" :placeholder="translate('primary identifier')" :value="productStoreSettings['productIdentificationPref'].primaryId" @ionChange="setProductIdentificationPref($event.detail.value, 'primaryId')"> <ion-select-option v-for="identification in productIdentifications" :key="identification" :value="identification" >{{ identification }}</ion-select-option> </ion-select> </ion-item> <ion-item> - <ion-select :label="translate('Secondary Product Identifier')" :disabled="!hasPermission(Actions.APP_PRODUCT_IDENTIFIER_UPDATE) || !(currentFacility?.productStore?.productStoreId || currentProductStore.productStoreId)" interface="popover" :placeholder="translate('secondary identifier')" :value="productStoreSettings['productIdentificationPref'].secondaryId" @ionChange="setProductIdentificationPref($event.detail.value, 'secondaryId')"> + <ion-select :label="translate('Secondary')" :disabled="!hasPermission(Actions.APP_PRODUCT_IDENTIFIER_UPDATE) || !(currentFacility?.productStore?.productStoreId || currentProductStore.productStoreId)" interface="popover" :placeholder="translate('secondary identifier')" :value="productStoreSettings['productIdentificationPref'].secondaryId" @ionChange="setProductIdentificationPref($event.detail.value, 'secondaryId')"> <ion-select-option v-for="identification in productIdentifications" :key="identification" :value="identification" >{{ identification }}</ion-select-option> <ion-select-option value="">{{ translate("None") }}</ion-select-option> </ion-select> @@ -153,8 +153,6 @@ const store = useStore() const appVersion = ref("") const appInfo = (process.env.VUE_APP_VERSION_INFO ? JSON.parse(process.env.VUE_APP_VERSION_INFO) : {}) as any -const productIdentifications = process.env.VUE_APP_PRDT_IDENT ? JSON.parse(process.env.VUE_APP_PRDT_IDENT) : [] - const userProfile = computed(() => store.getters["user/getUserProfile"]) const oms = computed(() => store.getters["user/getInstanceUrl"]) const omsRedirectionInfo = computed(() => store.getters["user/getOmsRedirectionInfo"]) @@ -163,9 +161,11 @@ const currentFacility = computed(() => store.getters["user/getCurrentFacility"]) const currentProductStore = computed(() => store.getters["user/getCurrentProductStore"]) const productStores = computed(() => store.getters["user/getProductStores"]) const productStoreSettings = computed(() => store.getters["user/getProductStoreSettings"]) +const productIdentifications = computed(() => store.getters["user/getGoodIdentificationTypes"]) -onMounted(() => { +onMounted(async () => { appVersion.value = appInfo.branch ? (appInfo.branch + "-" + appInfo.revision) : appInfo.tag; + await store.dispatch("user/fetchGoodIdentificationTypes") }) function logout() {