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

Improved: Show Completed Shipments Along with Open Shipments (#380) #381

Merged
merged 8 commits into from
Nov 6, 2024
6 changes: 4 additions & 2 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"Inventory can be received for purchase orders in multiple shipments. Proceeding will receive a new shipment for this purchase order but it will still be available for receiving later": "Inventory can be received for purchase orders in multiple shipments. { space } Proceeding will receive a new shipment for this purchase order but it will still be available for receiving later",
"item": "item",
"Item count": "Item count",
"Item count:": "Item count: {count}",
"items": "items",
"Load more returns": "Load more returns",
"Load more shipments": "Load more shipments",
Expand All @@ -63,12 +64,13 @@
"OMS": "OMS",
"OMS instance": "OMS instance",
"Only allow received quantity to be incremented by scanning the barcode of products. If the identifier is not found, the scan will default to using the internal name.": "Only allow received quantity to be incremented by scanning the barcode of products. {space} If the identifier is not found, the scan will default to using the internal name.",
"Open": "Open",
"ordered": "ordered",
"Orders not found": "Orders not found",
"Password": "Password",
"Pending: item": "Pending: {itemsCount} item",
"Pending: items": "Pending: {itemsCount} items",
"Please provide a valid valid barcode identifier.": "Please provide a valid valid barcode identifier.",
"Please provide a valid barcode identifier.": "Please provide a valid barcode identifier.",
"primary identifier": "primary identifier",
"Primary identifier": "Primary identifier",
"Primary Product Identifier": "Primary Product Identifier",
Expand Down Expand Up @@ -103,6 +105,7 @@
"Scan items": "Scan items",
"Scanned item is not present within the shipment:": "Scanned item is not present within the shipment: {itemName}",
"Scanned successfully.": "Scanned {itemName} successfully.",
"Search items": "Search items",
"secondary identifier": "secondary identifier",
"Secondary identifier": "Secondary identifier",
"Search": "Search",
Expand Down Expand Up @@ -136,7 +139,6 @@
"There are no purchase orders to receive": "There are no purchase orders to receive",
"There are no returns to receive": "There are no returns to receive",
"This is the name of the OMS you are connected to right now. Make sure that you are connected to the right instance before proceeding.": "This is the name of the OMS you are connected to right now. Make sure that you are connected to the right instance before proceeding.",
"This return has been and cannot be edited.": "This return has been {status} and cannot be edited.",
"Timezone": "Timezone",
"Time zone updated successfully": "Time zone updated successfully",
"To close the purchase order, select all.": "To close the purchase order, select all.",
Expand Down
16 changes: 1 addition & 15 deletions src/store/modules/order/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,23 +68,9 @@
}
commit(types.ORDER_CURRENT_PRODUCT_ADDED, product)
},
async getOrderDetail({ commit, state }, { orderId }) {

Check warning on line 71 in src/store/modules/order/actions.ts

View workflow job for this annotation

GitHub Actions / call-workflow-in-another-repo / reusable_workflow_job (18.x)

'state' is defined but never used

Check warning on line 71 in src/store/modules/order/actions.ts

View workflow job for this annotation

GitHub Actions / call-workflow-in-another-repo / reusable_workflow_job (20.x)

'state' is defined but never used
let resp;

const current = state.current as any
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is removed to fetch the data every time when we go to detail page, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, so as to avoid old item status issue due to slow Solr updation.

const orders = state.purchaseOrders.list as any

if (current.length && current[0]?.orderId === orderId) { return current }

else if(orders.length > 0) {
return orders.some((order: any) => {
if (order.doclist.docs[0]?.orderId === orderId) {
this.dispatch('product/fetchProductInformation', { order: order.doclist.docs });
commit(types.ORDER_CURRENT_UPDATED, { ...state.current, orderId: order.doclist.docs[0]?.orderId, externalOrderId: order.doclist.docs[0]?.externalOrderId, orderStatusId: order.doclist.docs[0]?.orderStatusId, orderStatusDesc: order.doclist.docs[0]?.orderStatusDesc, items: JSON.parse(JSON.stringify(order.doclist.docs)) })
return current;
}
})
}
try {
const payload = {
"json": {
Expand All @@ -96,7 +82,7 @@
},
"query": "docType:ORDER",
"filter": [
`orderTypeId: PURCHASE_ORDER AND orderId: ${orderId} AND orderStatusId: (ORDER_APPROVED OR ORDER_CREATED) AND facilityId: ${this.state.user.currentFacility.facilityId}`
`orderTypeId: PURCHASE_ORDER AND orderId: ${orderId} AND orderStatusId: (ORDER_APPROVED OR ORDER_CREATED OR ORDER_COMPLETED) AND facilityId: ${this.state.user.currentFacility.facilityId}`
]
}
}
Expand All @@ -120,7 +106,7 @@
}
return resp;
},
async createPurchaseShipment({ commit }, payload) {

Check warning on line 109 in src/store/modules/order/actions.ts

View workflow job for this annotation

GitHub Actions / call-workflow-in-another-repo / reusable_workflow_job (18.x)

'commit' is defined but never used

Check warning on line 109 in src/store/modules/order/actions.ts

View workflow job for this annotation

GitHub Actions / call-workflow-in-another-repo / reusable_workflow_job (20.x)

'commit' is defined but never used
let resp;
try {
const params = {
Expand Down
136 changes: 81 additions & 55 deletions src/views/PurchaseOrderDetail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<ion-button @click="receivingHistory()">
<ion-icon slot="icon-only" :icon="timeOutline"/>
</ion-button>
<ion-button :disabled="!hasPermission(Actions.APP_SHIPMENT_ADMIN)" @click="addProduct">
<ion-button :disabled="!hasPermission(Actions.APP_SHIPMENT_ADMIN) || isPOReceived()" @click="addProduct">
<ion-icon slot="icon-only" :icon="addOutline"/>
</ion-button>
</ion-buttons>
Expand All @@ -31,15 +31,15 @@

<div class="scanner">
<ion-item>
<ion-input :label="translate('Scan items')" label-placement="fixed" autofocus :placeholder="translate('Scan barcodes to receive them')" v-model="queryString" @keyup.enter="updateProductCount()" />
<ion-input :label="translate(isPOReceived() ? 'Search items' : 'Scan items')" label-placement="fixed" autofocus v-model="queryString" @keyup.enter="isPOReceived() ? searchProduct() : updateProductCount()" />
</ion-item>
<ion-button expand="block" fill="outline" @click="scan">
<ion-button expand="block" fill="outline" @click="scan" :disabled="isPOReceived()">
<ion-icon slot="start" :icon="cameraOutline" />
{{ translate("Scan") }}
</ion-button>
</div>

<ion-item lines="none">
<ion-item lines="none" v-if="!isPOReceived()">
<ion-label v-if="getPOItems('pending').length > 1" color="medium" class="ion-margin-end">
{{ translate("Pending: items", { itemsCount: getPOItems('pending').length }) }}
</ion-label>
Expand All @@ -48,57 +48,59 @@
</ion-label>
</ion-item>

<ion-card v-for="(item, index) in getPOItems('pending')" v-show="item.orderItemStatusId !== 'ITEM_COMPLETED' && item.orderItemStatusId !== 'ITEM_REJECTED'" :key="index" :class="item.internalName === lastScannedId ? 'scanned-item' : '' " :id="item.internalName">
<div class="product">
<div class="product-info">
<ion-item lines="none">
<ion-thumbnail slot="start" @click="openImage(getProduct(item.productId).mainImageUrl, getProduct(item.productId).productName)">
<DxpShopifyImg size="small" :src="getProduct(item.productId).mainImageUrl" />
</ion-thumbnail>
<ion-label class="ion-text-wrap">
<h2>{{ getProductIdentificationValue(productIdentificationPref.primaryId, getProduct(item.productId)) ? getProductIdentificationValue(productIdentificationPref.primaryId, getProduct(item.productId)) : getProduct(item.productId).productName }}</h2>
<p>{{ getProductIdentificationValue(productIdentificationPref.secondaryId, getProduct(item.productId)) }}</p>
</ion-label>
</ion-item>
</div>

<div class="location">
<LocationPopover :item="item" type="order" :facilityId="currentFacility.facilityId" />
</div>

<div class="product-count">
<ion-item>
<ion-input :label="translate('Qty')" label-placement="floating" type="number" value="0" min="0" v-model="item.quantityAccepted" :disabled="isForceScanEnabled" />
</ion-item>
</div>
</div>

<div class="action border-top" v-if="item.quantity > 0">
<div class="receive-all-qty">
<ion-button @click="receiveAll(item)" :disabled="isForceScanEnabled || isItemReceivedInFull(item)" slot="start" size="small" fill="outline">
{{ translate("Receive All") }}
</ion-button>
<template v-if="!isPOReceived()">
<ion-card v-for="(item, index) in getPOItems('pending')" v-show="item.orderItemStatusId !== 'ITEM_COMPLETED' && item.orderItemStatusId !== 'ITEM_REJECTED'" :key="index" :class="item.internalName === lastScannedId ? 'scanned-item' : '' " :id="item.internalName">
<div class="product">
<div class="product-info">
<ion-item lines="none">
<ion-thumbnail slot="start" @click="openImage(getProduct(item.productId).mainImageUrl, getProduct(item.productId).productName)">
<DxpShopifyImg size="small" :src="getProduct(item.productId).mainImageUrl" />
</ion-thumbnail>
<ion-label class="ion-text-wrap">
<h2>{{ getProductIdentificationValue(productIdentificationPref.primaryId, getProduct(item.productId)) ? getProductIdentificationValue(productIdentificationPref.primaryId, getProduct(item.productId)) : getProduct(item.productId).productName }}</h2>
<p>{{ getProductIdentificationValue(productIdentificationPref.secondaryId, getProduct(item.productId)) }}</p>
</ion-label>
</ion-item>
</div>

<div class="location">
<LocationPopover :item="item" type="order" :facilityId="currentFacility.facilityId" />
</div>

<div class="product-count">
<ion-item>
<ion-input :label="translate('Qty')" label-placement="floating" type="number" value="0" min="0" v-model="item.quantityAccepted" :disabled="isForceScanEnabled" />
</ion-item>
</div>
</div>

<div class="qty-progress">
<!-- TODO: improve the handling of quantityAccepted -->
<ion-progress-bar :color="getRcvdToOrderedFraction(item) === 1 ? 'success' : getRcvdToOrderedFraction(item) > 1 ? 'danger' : 'primary'" :value="getRcvdToOrderedFraction(item)" />
<div class="action border-top" v-if="item.quantity > 0">
<div class="receive-all-qty">
<ion-button @click="receiveAll(item)" :disabled="isForceScanEnabled || isItemReceivedInFull(item)" slot="start" size="small" fill="outline">
{{ translate("Receive All") }}
</ion-button>
</div>

<div class="qty-progress">
<!-- TODO: improve the handling of quantityAccepted -->
<ion-progress-bar :color="getRcvdToOrderedFraction(item) === 1 ? 'success' : getRcvdToOrderedFraction(item) > 1 ? 'danger' : 'primary'" :value="getRcvdToOrderedFraction(item)" />
</div>

<div class="po-item-history">
<ion-chip outline @click="receivingHistory(item.productId)">
<ion-icon :icon="checkmarkDone"/>
<ion-label> {{ getPOItemAccepted(item.productId) }} {{ translate("received") }} </ion-label>
</ion-chip>
</div>

<div class="qty-ordered">
<ion-label>{{ item.quantity }} {{ translate("ordered") }}</ion-label>
</div>
</div>
</ion-card>
</template>

<div class="po-item-history">
<ion-chip outline @click="receivingHistory(item.productId)">
<ion-icon :icon="checkmarkDone"/>
<ion-label> {{ getPOItemAccepted(item.productId) }} {{ translate("received") }} </ion-label>
</ion-chip>
</div>

<div class="qty-ordered">
<ion-label>{{ item.quantity }} {{ translate("ordered") }}</ion-label>
</div>
</div>
</ion-card>

<ion-item lines="none">
<ion-item lines="none" v-if="!isPOReceived()">
<ion-text v-if="getPOItems('completed').length > 1" color="medium" class="ion-margin-end">
{{ translate("Completed: items", { itemsCount: getPOItems('completed').length }) }}
</ion-text>
Expand Down Expand Up @@ -142,7 +144,7 @@
</main>
</ion-content>

<ion-footer>
<ion-footer v-if="!isPOReceived()">
<ion-toolbar>
<ion-buttons slot="end">
<ion-button fill="outline" size="small" color="primary" :disabled="!hasPermission(Actions.APP_SHIPMENT_UPDATE)" class="ion-margin-end" @click="closePO">{{ translate("Receive And Close") }}</ion-button>
Expand Down Expand Up @@ -265,7 +267,7 @@ export default defineComponent({
if(this.queryString) payload = this.queryString

if(!payload) {
showToast(translate("Please provide a valid valid barcode identifier."))
showToast(translate("Please provide a valid barcode identifier."))
return;
}
const result = await this.store.dispatch('order/updateProductCount', payload)
Expand Down Expand Up @@ -305,6 +307,24 @@ export default defineComponent({
}
this.queryString = ''
},
searchProduct() {
if(!this.queryString) {
showToast(translate("Please provide a valid barcode identifier."))
return;
}
const scannedElement = document.getElementById(this.queryString);
if(scannedElement) {
this.lastScannedId = this.queryString
scannedElement.scrollIntoView()
// Scanned product should get un-highlighted after 3s for better experience hence adding setTimeOut
setTimeout(() => {
this.lastScannedId = ''
}, 3000)
} else {
showToast(translate("Scanned item is not present within the shipment:", { itemName: this.queryString }));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Searched item...." instead of "Scanned item...."

}
this.queryString = ''
},
getPOItems(orderType: string) {
if(orderType === 'completed'){
return this.order.items.filter((item: any) => item.orderItemStatusId === 'ITEM_COMPLETED')
Expand Down Expand Up @@ -378,11 +398,17 @@ export default defineComponent({
return true;
}
})
},
isPOReceived() {
return this.order.orderStatusId === "ORDER_COMPLETED"
}
},
ionViewWillEnter() {
this.store.dispatch("order/getOrderDetail", { orderId: this.$route.params.slug }).then(() => {
this.store.dispatch('order/getPOHistory', { orderId: this.order.orderId })
this.store.dispatch("order/getOrderDetail", { orderId: this.$route.params.slug }).then(async () => {
await this.store.dispatch('order/getPOHistory', { orderId: this.order.orderId })
if(this.isPOReceived()) {
this.showCompletedItems = true;
}
})
},
setup() {
Expand Down
38 changes: 33 additions & 5 deletions src/views/PurchaseOrders.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,21 @@
<ion-menu-button slot="start" />
<ion-title>{{ translate("Purchase Orders") }}</ion-title>
</ion-toolbar>
<div>
<ion-searchbar :placeholder="translate('Search purchase orders')" v-model="queryString" @keyup.enter="queryString = $event.target.value; getPurchaseOrders()" />

<ion-segment v-model="selectedSegment" @ionChange="segmentChanged()">
<ion-segment-button value="open">
<ion-label>{{ translate("Open") }}</ion-label>
</ion-segment-button>
<ion-segment-button value="completed">
<ion-label>{{ translate("Completed") }}</ion-label>
</ion-segment-button>
</ion-segment>
</div>
</ion-header>
<ion-content>
<main>
<ion-searchbar :placeholder="translate('Search purchase orders')" v-model="queryString" @keyup.enter="queryString = $event.target.value; getPurchaseOrders()" />

<PurchaseOrderItem v-for="(order, index) in orders" :key="index" :purchaseOrder="order.doclist.docs[0]" />

<div v-if="orders.length" class="load-more-action ion-text-center">
Expand Down Expand Up @@ -44,11 +54,14 @@ import {
IonContent,
IonHeader,
IonIcon,
IonLabel,
IonMenuButton,
IonPage,
IonRefresher,
IonRefresherContent,
IonSearchbar,
IonSegment,
IonSegmentButton,
IonTitle,
IonToolbar
} from '@ionic/vue';
Expand All @@ -65,11 +78,14 @@ export default defineComponent({
IonContent,
IonHeader,
IonIcon,
IonLabel,
IonMenuButton,
IonPage,
IonRefresher,
IonRefresherContent,
IonSearchbar,
IonSegment,
IonSegmentButton,
IonTitle,
IonToolbar,
PurchaseOrderItem
Expand All @@ -78,7 +94,8 @@ export default defineComponent({
return {
queryString: '',
fetchingOrders: false,
showErrorMessage: false
showErrorMessage: false,
selectedSegment: "open"
}
},
computed: {
Expand All @@ -105,7 +122,7 @@ export default defineComponent({
"group.ngroups": true,
} as any,
"query": "*:*",
"filter": `docType: ORDER AND orderTypeId: PURCHASE_ORDER AND orderStatusId: (ORDER_APPROVED OR ORDER_CREATED) AND facilityId: ${this.currentFacility.facilityId}`
"filter": `docType: ORDER AND orderTypeId: PURCHASE_ORDER AND orderStatusId: ${this.selectedSegment === 'open' ? '(ORDER_APPROVED OR ORDER_CREATED)' : 'ORDER_COMPLETED'} AND facilityId: ${this.currentFacility.facilityId}`
}
}
if(this.queryString) {
Expand All @@ -129,6 +146,9 @@ export default defineComponent({
if (event) event.target.complete();
})
},
segmentChanged() {
this.getPurchaseOrders();
}
},
ionViewWillEnter () {
this.getPurchaseOrders();
Expand All @@ -144,4 +164,12 @@ export default defineComponent({
}
}
});
</script>
</script>

<style scoped>
@media (min-width: 991px) {
ion-header > div {
display: flex;
}
}
</style>
Loading
Loading