Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented: support for websocket for realtime counting (#598) #599

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/composables/useWebSocketComposables.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// This module provides composable functions for managing NetSuite integrations, allowing for asynchronous operations such as
//adding, editing, updating, and removing NetSuite IDs based on specified integration mapping data.
declare module "@/composables/useWebSocketComposables" {
export function useWebSocketComposables(webSocketUrl: string): {
initWebSocket: () => Promise<void>;
tryReopen: () => Promise<void>;
registerListener: (topic: string, callback: any) => Promise<void>;
};
}
88 changes: 88 additions & 0 deletions src/composables/useWebSocketComposables.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { onIonViewDidLeave } from '@ionic/vue';
import { reactive } from 'vue';

export function useWebSocketComposables(webSocketUrl: string) {
const state = reactive({
webSocket: null,
currentTopic: null,
topicListener: null,
tryReopenCount: 0,
}) as any;

const initWebSocket = () => {
state.webSocket = new WebSocket(webSocketUrl);

state.webSocket.onopen = () => {
state.tryReopenCount = 0;

// Subscribe to all topics
if(state.currentTopic) {
state.webSocket.send(`subscribe:${state.currentTopic}`);
}
};

state.webSocket.onmessage = (event: any) => {
const jsonObj = JSON.parse(event.data);
if (jsonObj.topic === state.currentTopic && state.topicListener) {
state.topicListener(jsonObj);
}
};

state.webSocket.onclose = () => {
console.error('WebSocket closed');
setTimeout(() => tryReopen(), 30 * 1000);
};

state.webSocket.onerror = (event: any) => {
console.error('WebSocket error', event);
};
};

const tryReopen = () => {
if (
(!state.webSocket ||
state.webSocket.readyState === WebSocket.CLOSED ||
state.webSocket.readyState === WebSocket.CLOSING) &&
state.tryReopenCount < 6
) {
state.tryReopenCount += 1;
initWebSocket();
}
};

const registerListener = (topic: string, callback: any) => {
if (!state.webSocket) {
initWebSocket();
}

if (state.currentTopic !== topic) {
// Unsubscribe from the previous topic
if (state.currentTopic && state.webSocket.readyState === WebSocket.OPEN) {
state.webSocket.send(`unsubscribe:${state.currentTopic}`);
}

// Update the current topic and listener
state.currentTopic = topic;
state.topicListener = callback;

// Subscribe to the new topic
if (state.webSocket.readyState === WebSocket.OPEN) {
state.webSocket.send(`subscribe:${topic}`);
}
} else if (state.topicListener !== callback) {
// Update the callback if it has changed
state.topicListener = callback;
}
};

onIonViewDidLeave(() => {
if (state.webSocket) {
state.webSocket.onclose = null;
state.webSocket.close();
}
});

return {
registerListener
};
}
7 changes: 7 additions & 0 deletions src/store/modules/user/getters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ const getters: GetterTree <UserState, RootState> = {
},
getGoodIdentificationTypes(state) {
return state.goodIdentificationTypes;
},
getWebSocketUrl(state) {
let baseURL = state.instanceUrl
if(baseURL.startsWith("http")) {
baseURL = baseURL.replace(/https?:\/\/|\/api|\/+/g, "");
}
return `ws://${baseURL}/notws?api_key=${state.token}`;
}
}
export default getters;
27 changes: 27 additions & 0 deletions src/views/CountDetail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ import { paperPlaneOutline } from "ionicons/icons"
import Image from "@/components/Image.vue";
import router from "@/router"
import { onBeforeRouteLeave } from 'vue-router';
import { useWebSocketComposables } from '@/composables/useWebSocketComposables';

const store = useStore();

Expand All @@ -263,6 +264,8 @@ const cycleCountItems = computed(() => store.getters["count/getCycleCountItems"]
const userProfile = computed(() => store.getters["user/getUserProfile"])
const productStoreSettings = computed(() => store.getters["user/getProductStoreSettings"])
const currentItemIndex = computed(() => !product.value ? 0 : itemsList?.value.findIndex((item) => item.productId === product?.value.productId && item.importItemSeqId === product?.value.importItemSeqId));
const currentFacility = computed(() => store.getters["user/getCurrentFacility"])
const webSocketUrl = computed(() => store.getters["user/getWebSocketUrl"])

const itemsList = computed(() => {
if (selectedSegment.value === 'all') {
Expand All @@ -283,6 +286,8 @@ const itemsList = computed(() => {
}
});

const { registerListener } = useWebSocketComposables(webSocketUrl.value);

const props = defineProps(["id"]);
let selectedSegment = ref('all');
let cycleCount = ref([]);
Expand All @@ -303,6 +308,7 @@ onIonViewDidEnter(async() => {
previousItem = itemsList.value[0]
await store.dispatch("product/currentProduct", itemsList.value[0])
barcodeInput.value?.$el?.setFocus();
registerListener(currentFacility.value.facilityId, handleNewMessage);
emitter.emit("dismissLoader")
})

Expand Down Expand Up @@ -638,6 +644,27 @@ async function readyForReview() {
});
await alert.present();
}

function handleNewMessage(jsonObj) {
const updatedItem = jsonObj.message
if(updatedItem.inventoryCountImportId !== cycleCount.value.inventoryCountImportId) return;

const items = JSON.parse(JSON.stringify(cycleCountItems.value.itemList))
const currentItemIndex = items.findIndex((item) => item.productId === updatedItem.productId && item.importItemSeqId === updatedItem.importItemSeqId);

if(currentItemIndex !== -1) {
items[currentItemIndex] = updatedItem
} else {
store.dispatch("product/fetchProducts", { productIds: [updatedItem.productId] })
items.push(updatedItem)
}

store.dispatch('count/updateCycleCountItems', items);
if(product.value.productId === updatedItem.productId) {
store.dispatch('product/currentProduct', updatedItem);
}
}

</script>

<style scoped>
Expand Down
36 changes: 34 additions & 2 deletions src/views/HardCountDetail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ import { CountService } from "@/services/CountService";
import Image from "@/components/Image.vue";
import router from "@/router";
import MatchProductModal from "@/components/MatchProductModal.vue";
import { useWebSocketComposables } from '@/composables/useWebSocketComposables';

const store = useStore();

Expand All @@ -228,6 +229,8 @@ const userProfile = computed(() => store.getters["user/getUserProfile"])
const productStoreSettings = computed(() => store.getters["user/getProductStoreSettings"])
const defaultRecountUpdateBehaviour = computed(() => store.getters["count/getDefaultRecountUpdateBehaviour"])
const currentItemIndex = computed(() => !currentProduct.value ? 0 : currentProduct.value.scannedId ? itemsList.value?.findIndex((item: any) => item.scannedId === currentProduct.value.scannedId) : itemsList?.value.findIndex((item: any) => item.productId === currentProduct.value?.productId && item.importItemSeqId === currentProduct.value?.importItemSeqId));
const currentFacility = computed(() => store.getters["user/getCurrentFacility"])
const webSocketUrl = computed(() => store.getters["user/getWebSocketUrl"])

const itemsList = computed(() => {
if(selectedSegment.value === "all") {
Expand All @@ -239,6 +242,8 @@ const itemsList = computed(() => {
}
});

const { registerListener } = useWebSocketComposables(webSocketUrl.value);

const props = defineProps(["id"]);
const cycleCount = ref({}) as any;
const queryString = ref("");
Expand All @@ -250,7 +255,7 @@ const inputCount = ref("") as any;
const selectedCountUpdateType = ref("add");
const isScrolling = ref(false);
let isScanningInProgress = ref(false);

const productIdAdding = ref();

onIonViewDidEnter(async() => {
emitter.emit("presentLoader");
Expand All @@ -260,6 +265,7 @@ onIonViewDidEnter(async() => {
barcodeInputRef.value?.$el?.setFocus();
selectedCountUpdateType.value = defaultRecountUpdateBehaviour.value
window.addEventListener('beforeunload', handleBeforeUnload);
registerListener(currentFacility.value.facilityId, handleNewMessage);
emitter.emit("dismissLoader")
})

Expand Down Expand Up @@ -432,7 +438,10 @@ async function addProductToItemsList() {
async function findProductFromIdentifier(scannedValue: string ) {
const product = await store.dispatch("product/fetchProductByIdentification", { scannedValue })
let newItem = {} as any;
if(product?.productId) newItem = await addProductToCount(product.productId)
if(product?.productId) {
productIdAdding.value = product.productId
newItem = await addProductToCount(product.productId)
}

setTimeout(() => {
updateCurrentItemInList(newItem, scannedValue);
Expand Down Expand Up @@ -512,6 +521,7 @@ async function updateCurrentItemInList(newItem: any, scannedValue: string) {
items[currentItemIndex] = updatedItem

store.dispatch('count/updateCycleCountItems', items);
productIdAdding.value = ""
}

async function readyForReview() {
Expand Down Expand Up @@ -651,6 +661,7 @@ async function matchProduct(currentProduct: any) {
addProductModal.onDidDismiss().then(async (result) => {
if(result.data?.selectedProduct) {
const product = result.data.selectedProduct
productIdAdding.value = product.productId
const newItem = await addProductToCount(product.productId)
await updateCurrentItemInList(newItem, currentProduct.scannedId);
}
Expand All @@ -659,6 +670,27 @@ async function matchProduct(currentProduct: any) {
addProductModal.present();
}

function handleNewMessage(jsonObj: any) {
const updatedItem = jsonObj.message
if(updatedItem.inventoryCountImportId !== cycleCount.value.inventoryCountImportId) return;
if(productIdAdding.value === updatedItem.productId) return;

const items = JSON.parse(JSON.stringify(cycleCountItems.value.itemList))
const currentItemIndex = items.findIndex((item: any) => item.productId === updatedItem.productId && item.importItemSeqId === updatedItem.importItemSeqId);

if(currentItemIndex !== -1) {
items[currentItemIndex] = updatedItem
} else {
store.dispatch("product/fetchProducts", { productIds: [updatedItem.productId] })
items.push(updatedItem)
}

store.dispatch('count/updateCycleCountItems', items);
if(currentProduct.value.productId === updatedItem.productId) {
store.dispatch('product/currentProduct', updatedItem);
}
}

function getVariance(item: any , isRecounting: boolean) {
const qty = item.quantity
if(isRecounting && inputCount.value === "") return 0;
Expand Down
Loading