Skip to content

Commit

Permalink
Merge pull request #434 from R-Sourabh/#429-force-scan-ui
Browse files Browse the repository at this point in the history
Implemented:  forced scanning UI and the functionality in the cycle count detail section(#429)
  • Loading branch information
ymaheshwari1 authored Nov 6, 2024
2 parents 6532e11 + 377b91f commit 3bc49f4
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 20 deletions.
41 changes: 41 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"mitt": "^2.1.0",
"register-service-worker": "^1.7.1",
"vue": "^3.2.26",
"vue-barcode-reader": "^1.0.3",
"vue-i18n": "~9.1.6",
"vue-logger-plugin": "^2.2.3",
"vue-router": "^4.0.12",
Expand Down
8 changes: 8 additions & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
"Filters": "Filters",
"File uploaded successfully": "File uploaded successfully",
"Force scan": "Force scan",
"Force scan enabled": "Force scan enabled",
"Go to Launchpad": "Go to Launchpad",
"Go to OMS": "Go to OMS",
"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.": "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.",
Expand Down Expand Up @@ -217,7 +218,12 @@
"Saved mappings": "Saved mappings",
"Saving recount will replace the existing count for item.": "Saving recount will replace the existing count for item.",
"Scan": "Scan",
"Scanning": "Scanning",
"Scan items": "Scan items",
"Scanned item does not match current product": "Scanned item does not match current product",
"Scan or search products": "Scan or search products",
"Scan a valid product sku": "Scan a valid product sku",
"Scanned quantity": "Scanned quantity",
"selected": "selected",
"Select": "Select",
"Select fields": "Select fields",
Expand All @@ -240,13 +246,15 @@
"Select a different time zone": "Select a different time zone",
"Set facility": "Set facility",
"Settings": "Settings",
"Scan the barcode on each unit to increment the counted inventory": "Scan the barcode on each unit to increment the counted inventory",
"Shopify Config": "Shopify Config",
"Show systemic inventory": "Show systemic inventory",
"Show the current physical quantity expected at locations while counting to help gauge inventory accuracy.": "Show the current physical quantity expected at locations while counting to help gauge inventory accuracy.",
"SKU": "SKU",
"Some of the mapping fields are missing in the CSV:" : "Some of the mapping fields are missing in the CSV: {missingFields}",
"Some of the item(s) are failed to accept": "Some of the item(s) are failed to accept",
"Something went wrong": "Something went wrong",
"Something went wrong, please try again": "Something went wrong, please try again",
"Something went wrong while login. Please contact administrator": "Something went wrong while login. Please contact administrator.",
"Specify which facility you want to operate from. Order, inventory and other configuration data will be specific to the facility you select.": "Specify which facility you want to operate from. Order, inventory and other configuration data will be specific to the facility you select.",
"Status": "Status",
Expand Down
3 changes: 3 additions & 0 deletions src/store/modules/product/getters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ const getters: GetterTree<ProductState, RootState> = {
// Returning empty object so that it doesn't breaks the UI
return state.cached[productId] ? state.cached[productId] : {};
},
getCachedProducts(state) {
return state.cached ? state.cached : {}
},
getCurrentProduct(state) {
return state.currentProduct
},
Expand Down
159 changes: 139 additions & 20 deletions src/views/CountDetail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,30 @@
</ion-list>
<template v-else>
<ion-list v-if="product.isRecounting">
<ion-item>
<ion-input :label="translate('Count')" :placeholder="translate('submit physical count')" name="value" v-model="inputCount" id="value" type="number" min="0" required @ionInput="calculateVariance" @keydown="inputCountValidation"/>
</ion-item>
<!-- force scan -->
<template v-if="productStoreSettings['forceScan']">
<ion-item lines="none">
<ion-label slot="start">
{{ translate('Force scan enabled') }}
<p>{{ translate("Scan the barcode on each unit to increment the counted inventory") }}</p>
</ion-label>
<input type="text" class="hidden-input" v-model="scannedCount" ref="barcodeInput" @change="handleInput" @blur="handleBlur"/>
<ion-button slot="end" expand="block" fill="outline" @click="focusInput">
<ion-icon slot="start" :icon="cameraOutline" />
{{ translate( isInputFocused ? "Scanning" : "Scan") }}
</ion-button>
</ion-item>
<ion-item>
<ion-label>{{ translate("Count") }}</ion-label>
<ion-label slot="end">{{ inputCount }}</ion-label>
</ion-item>
</template>
<template v-else>
<ion-item>
<ion-input :label="translate('Count')" :placeholder="translate('submit physical count')" name="value" v-model="inputCount" id="value" type="number" min="0" required @ionInput="calculateVariance" @keydown="inputCountValidation"/>
</ion-item>
</template>
<template v-if="productStoreSettings['showQoh']">
<ion-item>
{{ translate("Current on hand") }}
Expand Down Expand Up @@ -165,9 +186,30 @@
</ion-list>
<ion-list v-else>
<ion-item>
<ion-input :label="translate('Count')" :placeholder="translate('submit physical count')" name="value" v-model="inputCount" id="value" type="number" min="0" required @ionInput="calculateVariance" @keydown="inputCountValidation"/>
</ion-item>
<!-- force scan -->
<template v-if="productStoreSettings['forceScan']">
<ion-item lines="none">
<ion-label slot="start">
{{ translate('Force scan enabled') }}
<p>{{ translate("Scan the barcode on each unit to increment the counted inventory") }}</p>
</ion-label>
<input type="text" class="hidden-input" v-model="scannedCount" ref="barcodeInput" @change="handleInput" @blur="handleBlur"/>
<ion-button slot="end" fill="outline" @click="focusInput">
<ion-icon slot="start" :icon="cameraOutline" />
{{ translate( isInputFocused ? "Scanning" : "Scan") }}
</ion-button>
</ion-item>
<ion-item>
<ion-label>{{ translate("Count") }}</ion-label>
<ion-label slot="end">{{ inputCount === '' ? 0 : inputCount }}</ion-label>
</ion-item>
</template>
<template v-else>
<ion-item>
<ion-input :label="translate('Count')" :placeholder="translate('submit physical count')" name="value" v-model="inputCount" id="value" type="number" min="0" required @ionInput="calculateVariance" @keydown="inputCountValidation"/>
</ion-item>
</template>
<template v-if="productStoreSettings['showQoh']">
<ion-item>
{{ translate("Current on hand") }}
Expand All @@ -178,7 +220,7 @@
<ion-label slot="end">{{ variance }}</ion-label>
</ion-item>
</template>
<ion-button v-if="!['INV_COUNT_REJECTED', 'INV_COUNT_COMPLETED'].includes(product.itemStatusId)" class="ion-margin" expand="block" @click="saveCount()">
<ion-button v-if="!['INV_COUNT_REJECTED', 'INV_COUNT_COMPLETED'].includes(product.itemStatusId)" class="ion-margin" expand="block" @click="saveCount(product)">
{{ translate("Save count") }}
</ion-button>
</ion-list>
Expand Down Expand Up @@ -223,7 +265,7 @@ import {
onIonViewDidEnter,
alertController
} from '@ionic/vue';
import { chevronDownCircleOutline, chevronUpCircleOutline } from "ionicons/icons";
import { cameraOutline, chevronDownCircleOutline, chevronUpCircleOutline } from "ionicons/icons";
import { translate } from '@/i18n'
import { computed, defineProps, ref, onUpdated } from 'vue';
import { useStore } from "@/store";
Expand All @@ -242,6 +284,7 @@ const store = useStore();
const product = computed(() => store.getters['product/getCurrentProduct']);
const getProduct = computed(() => (id) => store.getters["product/getProduct"](id))
const getCachedProducts = computed(() => store.getters["product/getCachedProducts"])
const cycleCountItems = computed(() => store.getters["count/getCycleCountItems"]);
const userProfile = computed(() => store.getters["user/getUserProfile"])
const productStoreSettings = computed(() => store.getters["user/getProductStoreSettings"])
Expand Down Expand Up @@ -269,13 +312,17 @@ const props = defineProps(["id"]);
let selectedSegment = ref('all');
let cycleCount = ref([]);
const queryString = ref('');
const barcodeInput = ref(null);
let filteredItems = ref([]);
const inputCount = ref('');
const scannedCount = ref('')
const variance = ref(0);
const isFirstItem = ref(true);
const isLastItem = ref(false);
const isScrolling = ref(false);
const isInputFocused = ref(false)
let previousItem = null;
// Update variance value when component is updated, ensuring it's prefilled with correct value when page loads.
onUpdated(() => {
Expand All @@ -290,10 +337,49 @@ onIonViewDidEnter(async() => {
selectedSegment.value = 'all';
queryString.value = '';
updateFilteredItems();
previousItem = itemsList.value[0]
await store.dispatch("product/currentProduct", itemsList.value[0])
updateNavigationState(0);
})
async function focusInput() {
barcodeInput.value.focus();
isInputFocused.value = true;
}
function handleBlur() {
isInputFocused.value = false;
}
async function handleInput(event) {
if (!isInputFocused.value) return;
let sku = event.target.value;
if(!sku) {
showToast(translate("Scan a valid product sku"));
return;
}
const cachedProducts = getCachedProducts.value;
let scannedItemId = Object.keys(cachedProducts).find(productId => cachedProducts[productId].sku === sku);
if (!scannedItemId) {
const product = await findProduct(sku);
if(product) {
scannedItemId = product.data.response?.docs[0]?.productId
}
}
if (scannedItemId) {
if (scannedItemId === product.value.productId) {
inputCount.value++;
} else {
showToast(translate('Scanned item does not match current product'));
}
} else {
showToast(translate('Product not found'));
}
scannedCount.value = ''
}
function inputCountValidation(event) {
if(/[`!@#$%^&*()_+\-=\\|,.<>?~e]/.test(event.key) && event.key !== 'Backspace') event.preventDefault();
}
Expand Down Expand Up @@ -369,6 +455,12 @@ const onScroll = (event) => {
const productId = entry.target.dataset.productId;
const seqId = entry.target.dataset.seq;
const currentProduct = filteredItems.value.find((item) => item.productId === productId && item.importItemSeqId === seqId);
if(previousItem.productId !== currentProduct.productId || previousItem.importItemSeqId !== currentProduct.importItemSeqId) {
if(inputCount.value) saveCount(previousItem);
}
previousItem = currentProduct // Update the previousItem variable with the current item
if (currentProduct) {
const currentIndex = filteredItems.value.indexOf(currentProduct);
store.dispatch("product/currentProduct", currentProduct);
Expand Down Expand Up @@ -430,36 +522,36 @@ function getVariance(item , count) {
return item.itemStatusId === "INV_COUNT_REJECTED" ? 0 : parseInt(count ? count : qty) - parseInt(item.qoh)
}
async function saveCount() {
async function saveCount(currentProduct) {
if (!inputCount.value) {
showToast(translate("Enter a count before saving changes"))
return;
}
try {
const payload = {
inventoryCountImportId: product.value.inventoryCountImportId,
importItemSeqId: product.value.importItemSeqId,
productId: product.value.productId,
inventoryCountImportId: currentProduct.inventoryCountImportId,
importItemSeqId: currentProduct.importItemSeqId,
productId: currentProduct.productId,
quantity: inputCount.value,
countedByUserLoginId: userProfile.value.username
};
const resp = await CountService.updateCount(payload);
if (!hasError(resp)) {
product.value.quantity = inputCount.value
product.value.countedByGroupName = userProfile.value.userFullName
product.value.countedByUserLoginId = userProfile.value.username
currentProduct.quantity = inputCount.value
currentProduct.countedByGroupName = userProfile.value.userFullName
currentProduct.countedByUserLoginId = userProfile.value.username
currentProduct.isRecounting = false;
inputCount.value = '';
product.value.isRecounting = false;
const items = JSON.parse(JSON.stringify(itemsList.value))
items.map((item) => {
if(item.importItemSeqId === product.value?.importItemSeqId) {
item.quantity = product.value.quantity
if(item.importItemSeqId === currentProduct.importItemSeqId) {
item.quantity = currentProduct.quantity
item.countedByGroupName = userProfile.value.userFullName
item.countedByUserLoginId = userProfile.value.username
}
})
await store.dispatch('count/updateCycleCountItems', items);
await store.dispatch('product/currentProduct', product.value);
await store.dispatch('product/currentProduct', currentProduct);
} else {
throw resp.data;
}
Expand Down Expand Up @@ -505,7 +597,7 @@ async function openRecountSaveAlert() {
{
text: translate('Save Re-count'),
handler: async () => {
await saveCount();
await saveCount(product.value);
}
}]
});
Expand Down Expand Up @@ -557,10 +649,37 @@ async function readyForReview() {
});
await alert.present();
}
async function findProduct(sku) {
if(!sku) return;
let resp;
const viewSize = 1, viewIndex = 0;
try {
resp = await store.dispatch("product/findProduct", { queryString: sku, viewSize, viewIndex})
if (!hasError(resp)) {
return resp;
} else {
throw resp.data
}
} catch(err) {
logger.error("Product not found", err)
}
}
</script>
<style scoped>
ion-list {
min-width: 400px;
}
.hidden-input {
position: absolute;
opacity: 0
}
.find {
display: grid;
height: 100%;
Expand Down

0 comments on commit 3bc49f4

Please sign in to comment.