Skip to content

Commit

Permalink
Merge pull request #258 from amansinghbais/receiving/#212
Browse files Browse the repository at this point in the history
Implemented: functionality which allows users to manually close purchase order items when they receive them. (#212)
  • Loading branch information
ravilodhi authored Oct 20, 2023
2 parents 8a97939 + 6cd37f6 commit e90e189
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 12 deletions.
186 changes: 186 additions & 0 deletions src/components/ClosePurchaseOrderModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
<template>
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button @click="closeModal">
<ion-icon slot="icon-only" :icon="arrowBackOutline" />
</ion-button>
</ion-buttons>
<ion-title>{{ $t("Close purchase order items") }}</ion-title>
<ion-buttons slot="end" @click="selectAllItems">
<ion-button color="primary">{{ $t("Select all") }}</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>

<ion-content>
<ion-item lines="none">
<ion-list-header>{{ $t("To close the purchase order, select all.") }}</ion-list-header>
</ion-item>
<ion-list>
<ion-item :button="isPOItemStatusPending(item)" v-for="(item, index) in getPOItems()" :key="index" @click="item.isChecked = !item.isChecked">
<ion-thumbnail slot="start">
<ShopifyImg size="small" :src="getProduct(item.productId).mainImageUrl" />
</ion-thumbnail>
<ion-label>
<h2>{{ productHelpers.getProductIdentificationValue(productIdentificationPref.primaryId, getProduct(item.productId)) }}</h2>
<p>{{ productHelpers.getProductIdentificationValue(productIdentificationPref.secondaryId, getProduct(item.productId)) }}</p>
</ion-label>
<ion-buttons>
<ion-badge v-if="item.orderItemStatusId === 'ITEM_COMPLETED'" slot="end">{{ $t("Completed") }}</ion-badge>
<ion-badge v-else-if="item.orderItemStatusId === 'ITEM_REJECTED'" color="danger" slot="end">{{ $t("Rejected") }}</ion-badge>
<ion-checkbox v-else slot="end" :modelValue="item.isChecked" />
</ion-buttons>
</ion-item>
</ion-list>
</ion-content>

<ion-fab vertical="bottom" horizontal="end" slot="fixed">
<ion-fab-button :disabled="!hasPermission(Actions.APP_SHIPMENT_UPDATE) || !isEligibleToClosePOItems()" @click="confirmSave">
<ion-icon :icon="saveOutline" />
</ion-fab-button>
</ion-fab>
</template>

<script lang="ts">
import {
IonBadge,
IonButton,
IonButtons,
IonCheckbox,
IonContent,
IonFab,
IonFabButton,
IonHeader,
IonIcon,
IonItem,
IonLabel,
IonList,
IonListHeader,
IonTitle,
IonToolbar,
IonThumbnail,
alertController,
modalController
} from '@ionic/vue';
import { Actions, hasPermission } from '@/authorization'
import { closeOutline, checkmarkCircle, arrowBackOutline, saveOutline } from 'ionicons/icons';
import { defineComponent } from 'vue';
import { mapGetters, useStore } from 'vuex'
import { OrderService } from "@/services/OrderService";
import { productHelpers } from '@/utils';
import { ShopifyImg } from '@hotwax/dxp-components';
import { useRouter } from 'vue-router';
export default defineComponent({
name: "ClosePurchaseOrderModal",
components: {
IonBadge,
IonButton,
IonButtons,
IonCheckbox,
IonContent,
IonFab,
IonFabButton,
IonHeader,
IonIcon,
IonItem,
IonLabel,
IonList,
IonListHeader,
IonTitle,
IonThumbnail,
IonToolbar,
ShopifyImg
},
computed: {
...mapGetters({
getProduct: 'product/getProduct',
order: 'order/getCurrent',
productIdentificationPref: 'user/getProductIdentificationPref'
})
},
props: ['isEligibileForCreatingShipment'],
methods: {
closeModal() {
modalController.dismiss({ dismissed: true });
},
async confirmSave() {
const alert = await alertController.create({
header: this.$t('Close purchase order items'),
message: this.$t('Are you sure you have received the purchase order for the selected items? Once closed, the shipments for the selected items wont be available for receiving later.', { space: '<br /><br />' }),
buttons: [{
text: this.$t('Cancel'),
role: 'cancel'
},
{
text: this.$t('Proceed'),
role: 'proceed',
handler: async() => {
await this.updatePOItemStatus()
modalController.dismiss()
this.router.push('/purchase-orders')
}
}]
});
return alert.present();
},
async updatePOItemStatus() {
// Shipment can only be created if quantity is specified for atleast one PO item.
// In some cases we don't need to create shipment instead directly need to close PO items.
if(this.isEligibileForCreatingShipment) {
const eligibleItemsForShipment = this.order.items.filter((item: any) => item.quantityAccepted > 0)
await this.store.dispatch('order/createPurchaseShipment', { items: eligibleItemsForShipment, orderId: this.order.orderId })
}
const eligibleItems = this.order.items.filter((item: any) => item.isChecked && this.isPOItemStatusPending(item))
const responses = await Promise.allSettled(eligibleItems.map(async (item: any) => {
await OrderService.updatePOItemStatus({
orderId: item.orderId,
orderItemSeqId: item.orderItemSeqId,
statusId: "ITEM_COMPLETED"
})
}))
const failedItemsCount = responses.filter((response) => response.status === 'rejected').length
if(failedItemsCount){
console.error('Failed to update the status of purchase order items.')
}
},
isEligibleToClosePOItems() {
return this.order.items.some((item: any) => item.isChecked && this.isPOItemStatusPending(item))
},
isPOItemStatusPending(item: any) {
return item.orderItemStatusId !== "ITEM_COMPLETED" && item.orderItemStatusId !== "ITEM_REJECTED"
},
selectAllItems() {
this.order.items.map((item:any) => {
// Purchase Order may contains items without orderId, there status can't be updated
// Hence not allowing to select those items.
if(item.orderId && this.isPOItemStatusPending(item)) {
item.isChecked = true;
}
})
},
getPOItems() {
return this.order.items.filter((item: any) => item.orderId)
}
},
setup() {
const router = useRouter()
const store = useStore()
return {
arrowBackOutline,
Actions,
closeOutline,
checkmarkCircle,
hasPermission,
OrderService,
productHelpers,
router,
saveOutline,
store
};
}
});
</script>
8 changes: 8 additions & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,20 @@
"Add to Shipment": "Add to Shipment",
"App": "App",
"Authenticating": "Authenticating",
"Are you sure you have received the purchase order for the selected items? Once closed, the shipments for the selected items wont be available for receiving later.": "Are you sure you have received the purchase order for the selected items? { space } Once closed, the shipments for the selected items won't be available for receiving later.",
"Are you sure you want to change the time zone to?": "Are you sure you want to change the time zone to {timeZoneId}?",
"Arrival date": "Arrival date",
"Cancel": "Cancel",
"Change": "Change",
"Choosing a product identifier allows you to view products with your preferred identifiers.": "Choosing a product identifier allows you to view products with your preferred identifiers.",
"Click the backdrop to dismiss.": "Click the backdrop to dismiss.",
"Complete": "Complete",
"Completed": "Completed",
"COMPLETED: ITEM": "COMPLETED: {itemsCount} ITEM",
"COMPLETED: ITEMS": "COMPLETED: {itemsCount} ITEMS",
"Confirm": "Confirm",
"Copied": "Copied { value }",
"Close purchase order items": "Close purchase order items",
"eCom Store": "eCom Store",
"Enter a custom date time format that you want to use when uploading documents to HotWax Commerce.": "Enter a custom date time format that you want to use when uploading documents to HotWax Commerce.",
"External ID": "External ID",
Expand Down Expand Up @@ -64,7 +67,9 @@
"Purchase Order Details": "Purchase Order Details",
"Purchase Orders": "Purchase Orders",
"Qty": "Qty",
"Receive": "Receive",
"Receive All": "Receive All",
"Receive And Close": "Receive And Close",
"Receive inventory": "Receive inventory",
"received": "received",
"Receive Shipment": "Receive Shipment",
Expand All @@ -76,6 +81,7 @@
"Returns": "Returns",
"Returns not found": "Returns not found",
"rejected": "rejected",
"Rejected": "Rejected",
"returned": "returned",
"Scan": "Scan",
"Scan ASN to start receiving": "Scan ASN to start receiving",
Expand All @@ -87,6 +93,7 @@
"Search time zones": "Search time zones",
"Search SKU or product name": "Search SKU or product name",
"Secondary Product Identifier": "Secondary Product Identifier",
"Select all": "Select all",
"Select facility": "Select facility",
"Select store": "Select store",
"Select time zone": "Select time zone",
Expand All @@ -112,6 +119,7 @@
"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.",
"Unable to update product identifier preference": "Unable to update product identifier preference",
"Update time zone": "Update time zone",
"Username": "Username",
Expand Down
10 changes: 9 additions & 1 deletion src/services/OrderService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,18 @@ const fetchPOHistory = async (payload: any): Promise<any> => {
})
}

const updatePOItemStatus = async (payload: any): Promise<any> => {
return api({
url: "service/changeOrderItemStatus",
method: "POST",
data: payload
})
}

export const OrderService = {
fetchPurchaseOrders,
fetchPODetail,
createPurchaseShipment,
fetchPOHistory
fetchPOHistory,
updatePOItemStatus
}
34 changes: 23 additions & 11 deletions src/views/PurchaseOrderDetail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -142,13 +142,16 @@
</div>
</ion-card>
</main>

<ion-fab vertical="bottom" horizontal="end" slot="fixed">
<ion-fab-button :disabled="!hasPermission(Actions.APP_SHIPMENT_UPDATE) || !isEligibileForCreatingShipment()" @click="savePODetails">
<ion-icon :icon="saveOutline" />
</ion-fab-button>
</ion-fab>
</ion-content>

<ion-footer>
<ion-toolbar>
<ion-item slot="end">
<ion-button :disabled="!hasPermission(Actions.APP_SHIPMENT_UPDATE)" fill="outline" class="ion-margin-end" @click="closePO">{{ $t("Receive And Close") }}</ion-button>
<ion-button :disabled="!hasPermission(Actions.APP_SHIPMENT_UPDATE) || !isEligibileForCreatingShipment()" @click="savePODetails">{{ $t("Receive") }}</ion-button>
</ion-item>
</ion-toolbar>
</ion-footer>
</ion-page>
</template>

Expand All @@ -162,8 +165,7 @@ import {
IonChip,
IonContent,
IonHeader,
IonFab,
IonFabButton,
IonFooter,
IonIcon,
IonItem,
IonInput,
Expand All @@ -174,8 +176,8 @@ import {
IonThumbnail,
IonTitle,
IonToolbar,
modalController,
alertController,
modalController
} from '@ionic/vue';
import { defineComponent } from 'vue';
import { addOutline, cameraOutline, checkmarkDone, copyOutline, eyeOffOutline, eyeOutline, locationOutline, saveOutline, timeOutline } from 'ionicons/icons';
Expand All @@ -185,6 +187,7 @@ import { useStore, mapGetters } from 'vuex';
import { useRouter } from 'vue-router';
import Scanner from "@/components/Scanner.vue"
import AddProductToPOModal from '@/views/AddProductToPOModal.vue'
import ClosePurchaseOrderModal from '@/components/ClosePurchaseOrderModal.vue'
import LocationPopover from '@/components/LocationPopover.vue'
import ImageModal from '@/components/ImageModal.vue';
import { copyToClipboard, hasError, productHelpers } from '@/utils';
Expand All @@ -202,8 +205,7 @@ export default defineComponent({
IonChip,
IonContent,
IonHeader,
IonFab,
IonFabButton,
IonFooter,
IonIcon,
IonItem,
IonInput,
Expand Down Expand Up @@ -302,6 +304,16 @@ export default defineComponent({
});
return alert.present();
},
async closePO() {
const modal = await modalController.create({
component: ClosePurchaseOrderModal,
componentProps: {
isEligibileForCreatingShipment: this.isEligibileForCreatingShipment()
}
})
return modal.present();
},
async createShipment() {
const eligibleItems = this.order.items.filter((item: any) => item.quantityAccepted > 0)
const resp = await this.store.dispatch('order/createPurchaseShipment', { items: eligibleItems, orderId: this.order.orderId })
Expand Down

0 comments on commit e90e189

Please sign in to comment.