diff --git a/src/locales/en.json b/src/locales/en.json index 83e544bd4..d5b94ff14 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -23,6 +23,7 @@ "Choose language": "Choose language", "City": "City", "Click the backdrop to dismiss.": "Click the backdrop to dismiss.", + "Color:": "Color: {color}", "Colors": "Colors", "Complete": "Complete", "Completed": "Completed", @@ -114,6 +115,7 @@ "Orders": "Orders", "Orders Not Found": "Orders Not Found", "Order item rejection history": "Order item rejection history", + "Other shipments in this order": "Other shipments in this order", "Order reservations": "Order reservations", "order reservations at": "{ count } order reservations at { store }", "Other stores": "Other stores", @@ -123,6 +125,7 @@ "Packing slips help customer reconcile their order against the delivered items.": "Packing slips help customer reconcile their order against the delivered items.", "Partial Order rejection": "Partial Order rejection", "Password": "Password", + "Pending allocation": "Pending allocation", "pending approval": "pending approval", "Picked by": "Picked by { pickers }", "Pick up location": "Pick up location", @@ -169,6 +172,7 @@ "Ship to this address": "Ship to this address", "Shop": "Shop", "Show shipping orders": "Show shipping orders", + "Size:": "Size: {size}", "Sizes": "Sizes", "Something went wrong": "Something went wrong", "Something went wrong, could not edit picker.": "Something went wrong, could not edit picker.", diff --git a/src/locales/es.json b/src/locales/es.json index ec6171a2b..552436564 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -111,6 +111,7 @@ "Orders": "Órdenes", "Orders Not Found": "Órdenes no encontradas", "Order item rejection history": "Historial de rechazo de artículos de pedido", + "Other shipments in this order": "Other shipments in this order", "Order reservations": "Order reservations", "order reservations at": "{ count } order reservations at { store }", "Other stores": "Otras tiendas", @@ -120,6 +121,7 @@ "Packing slips help customer reconcile their order against the delivered items.": "Las remisiones de embalaje ayudan al cliente a conciliar su orden con los artículos entregados.", "Partial Order rejection": "Rechazo parcial del pedido", "Password": "Contraseña", + "Pending allocation": "Pending allocation", "pending approval": "pendiente de aprobación", "Picked by": "Recogido por { pickers }", "Pick up location": "Ubicación de recogida", diff --git a/src/services/OrderService.ts b/src/services/OrderService.ts index 2eee06c73..99de19717 100644 --- a/src/services/OrderService.ts +++ b/src/services/OrderService.ts @@ -266,9 +266,52 @@ const getShippingPhoneNumber = async (orderId: string): Promise => { return phoneNumber } +const findOrderShipGroup = async (query: any): Promise => { + return api({ + url: "solr-query", + method: "post", + data: query + }); +} + +const fetchTrackingCodes = async (shipmentIds: Array): Promise => { + let shipmentTrackingCodes = []; + const params = { + "entityName": "ShipmentPackageRouteSeg", + "inputFields": { + "shipmentId": shipmentIds, + "shipmentId_op": "in", + "shipmentItemSeqId_op": "not-empty" + }, + "fieldList": ["shipmentId", "shipmentPackageSeqId", "trackingCode"], + "viewSize": 250, // maximum records we could have + "distinct": "Y" + } + + try { + const resp = await api({ + url: "performFind", + method: "get", + params + }) + + if (!hasError(resp)) { + shipmentTrackingCodes = resp?.data.docs; + } else if (!resp?.data.error || (resp.data.error && resp.data.error !== "No record found")) { + return Promise.reject(resp?.data.error); + } + } catch (err) { + console.error('Failed to fetch tracking codes for shipments', err) + } + + return shipmentTrackingCodes; +} + export const OrderService = { fetchOrderItems, fetchOrderPaymentPreferences, + fetchTrackingCodes, + findOrderShipGroup, getOpenOrders, getOrderDetails, getCompletedOrders, diff --git a/src/services/UtilService.ts b/src/services/UtilService.ts index a80a263fe..f2b5f1749 100644 --- a/src/services/UtilService.ts +++ b/src/services/UtilService.ts @@ -33,6 +33,22 @@ const resetPicker = async (payload: any): Promise => { }) } +const fetchFacilityTypeInformation = async (query: any): Promise => { + return api({ + url: "performFind", + method: "get", + params: query + }); +} + +const fetchPartyInformation = async (query: any): Promise => { + return api({ + url: "performFind", + method: "get", + params: query + }); +} + const fetchReservedQuantity = async (query: any): Promise => { return api({ url: "solr-query", @@ -42,6 +58,8 @@ const fetchReservedQuantity = async (query: any): Promise => { } export const UtilService = { + fetchFacilityTypeInformation, + fetchPartyInformation, fetchPaymentMethodTypeDesc, fetchRejectReasons, fetchStatusDesc, diff --git a/src/store/modules/order/actions.ts b/src/store/modules/order/actions.ts index f4936f21c..73290ba64 100644 --- a/src/store/modules/order/actions.ts +++ b/src/store/modules/order/actions.ts @@ -9,6 +9,7 @@ import { translate } from "@hotwax/dxp-components"; import emitter from '@/event-bus' import store from "@/store"; import { prepareOrderQuery } from "@/utils/solrHelper"; +import { getOrderCategory } from "@/utils/order"; import logger from "@/logger"; const actions: ActionTree ={ @@ -168,7 +169,8 @@ const actions: ActionTree ={ return arr }, []), placedDate: orderItem.orderDate, - shippingInstructions: orderItem.shippingInstructions + shippingInstructions: orderItem.shippingInstructions, + shipGroupSeqId: orderItem.shipGroupSeqId } }) @@ -213,6 +215,7 @@ const actions: ActionTree ={ // As one order can have multiple parts thus checking orderId and partSeq as well before making any api call if(current.orderId === payload.orderId && current.orderType === orderType && current.part?.orderPartSeqId === payload.orderPartSeqId) { this.dispatch('product/getProductInformation', { orders: [ current ] }) + await dispatch('fetchShipGroupForOrder'); return current } if(orders.length) { @@ -304,8 +307,9 @@ const actions: ActionTree ={ await dispatch('updateCurrent', { order: currentOrder }) }, - updateCurrent ({ commit }, payload) { + async updateCurrent ({ commit, dispatch }, payload) { commit(types.ORDER_CURRENT_UPDATED, { order: payload.order }) + await dispatch('fetchShipGroupForOrder'); }, async getPackedOrders ({ commit, state }, payload) { @@ -370,7 +374,8 @@ const actions: ActionTree ={ ids.push(picker.split('/')[0]); return ids; }, [])) : "", - picklistId: orderItem.picklistId + picklistId: orderItem.picklistId, + shipGroupSeqId: orderItem.shipGroupSeqId } }) this.dispatch('product/getProductInformation', { orders }); @@ -439,7 +444,8 @@ const actions: ActionTree ={ return arr }, []), - placedDate: orderItem.orderDate + placedDate: orderItem.orderDate, + shipGroupSeqId: orderItem.shipGroupSeqId } }) this.dispatch('product/getProductInformation', { orders }); @@ -1010,6 +1016,156 @@ const actions: ActionTree ={ } commit(types.ORDER_CURRENT_UPDATED, { order }); }, + + async fetchShipGroupForOrder({ dispatch, state }) { + const order = JSON.parse(JSON.stringify(state.current)) + + // return if orderId is not found on order + if (!order?.orderId) { + return; + } + + const params = { + groupBy: 'shipGroupSeqId', + 'shipGroupSeqId': '[* TO *]', // check to ignore all those records for which shipGroupSeqId is not present + '-shipGroupSeqId': order.shipGroupSeqId, + orderId: order.orderId, + docType: 'ORDER' + } + + const orderQueryPayload = prepareOrderQuery(params) + + let resp, total, shipGroups = []; + const facilityTypeIds: Array = []; + + try { + resp = await OrderService.findOrderShipGroup(orderQueryPayload); + + if (resp.status === 200 && !hasError(resp) && resp.data.grouped?.shipGroupSeqId.matches > 0) { + shipGroups = resp.data.grouped.shipGroupSeqId.groups + } else { + throw resp.data + } + } catch (err) { + console.error('Failed to fetch ship group information for order', err) + } + + // return if shipGroups are not found for order + if (!shipGroups.length) { + return; + } + + shipGroups = shipGroups.map((shipGroup: any) => { + const shipItem = shipGroup?.doclist?.docs[0] + + if (!shipItem) { + return; + } + + // In some case we are not having facilityTypeId in resp, resulting in undefined being pushed in the array + // so checking for facilityTypeId before updating the array + shipItem.facilityTypeId && facilityTypeIds.push(shipItem.facilityTypeId) + + return { + items: shipGroup.doclist.docs, + facilityId: shipItem.facilityId, + facilityTypeId: shipItem.facilityTypeId, + facilityName: shipItem.facilityName, + shippingMethod: shipItem.shippingMethod, + orderId: shipItem.orderId, + shipGroupSeqId: shipItem.shipGroupSeqId + } + }) + + this.dispatch('util/fetchFacilityTypeInformation', facilityTypeIds) + + // fetching reservation information for shipGroup from OISGIR doc + await dispatch('fetchAdditionalShipGroupForOrder', { shipGroups }); + }, + + async fetchAdditionalShipGroupForOrder({ commit, state }, payload) { + const order = JSON.parse(JSON.stringify(state.current)) + + + // return if orderId is not found on order + if (!order?.orderId) { + return; + } + + const shipGroupSeqIds = payload.shipGroups.map((shipGroup: any) => shipGroup.shipGroupSeqId) + const orderId = order.orderId + + const params = { + groupBy: 'shipGroupSeqId', + 'shipGroupSeqId': `(${shipGroupSeqIds.join(' OR ')})`, + '-fulfillmentStatus': '(Rejected OR Cancelled)', + orderId: orderId + } + + const orderQueryPayload = prepareOrderQuery(params) + + let resp, total, shipGroups: any = []; + + try { + resp = await OrderService.findOrderShipGroup(orderQueryPayload); + if (resp.status === 200 && !hasError(resp) && resp.data.grouped?.shipGroupSeqId.matches > 0) { + total = resp.data.grouped.shipGroupSeqId.ngroups + shipGroups = resp.data.grouped.shipGroupSeqId.groups + } else { + throw resp.data + } + } catch (err) { + console.error('Failed to fetch ship group information for order', err) + } + + shipGroups = payload.shipGroups.map((shipGroup: any) => { + const reservedShipGroupForOrder = shipGroups.find((group: any) => shipGroup.shipGroupSeqId === group.doclist?.docs[0]?.shipGroupSeqId) + + const reservedShipGroup = reservedShipGroupForOrder?.groupValue ? reservedShipGroupForOrder.doclist.docs[0] : '' + + return reservedShipGroup ? { + ...shipGroup, + items: reservedShipGroupForOrder.doclist.docs, + carrierPartyId: reservedShipGroup.carrierPartyId, + shipmentId: reservedShipGroup.shipmentId, + category: getOrderCategory(reservedShipGroupForOrder.doclist.docs[0]) + } : { + ...shipGroup, + category: getOrderCategory(shipGroup.items[0]) + } + }) + + const carrierPartyIds: Array = []; + const shipmentIds: Array = []; + + + if (total) { + shipGroups.map((shipGroup: any) => { + if (shipGroup.shipmentId) shipmentIds.push(shipGroup.shipmentId) + if (shipGroup.carrierPartyId) carrierPartyIds.push(shipGroup.carrierPartyId) + }) + } + + try { + this.dispatch('util/fetchPartyInformation', carrierPartyIds) + const shipmentTrackingCodes = await OrderService.fetchTrackingCodes(shipmentIds) + + shipGroups.find((shipGroup: any) => { + const trackingCode = shipmentTrackingCodes.find((shipmentTrackingCode: any) => shipGroup.shipmentId === shipmentTrackingCode.shipmentId)?.trackingCode + + shipGroup.trackingCode = trackingCode; + }) + } catch (err) { + console.error('Failed to fetch information for ship groups', err) + } + + this.dispatch('product/getProductInformation', { orders: [{ parts: shipGroups }] }) + + order['shipGroups'] = shipGroups + + commit(types.ORDER_CURRENT_UPDATED, {order}) + return shipGroups; + }, } export default actions; diff --git a/src/store/modules/util/UtilState.ts b/src/store/modules/util/UtilState.ts index 31cc2f92c..fa86a09d2 100644 --- a/src/store/modules/util/UtilState.ts +++ b/src/store/modules/util/UtilState.ts @@ -2,4 +2,6 @@ export default interface UtilState { rejectReasons: []; paymentMethodTypeDesc: any; statusDesc: any; + facilityTypeDesc: any; + partyNames: any; } \ No newline at end of file diff --git a/src/store/modules/util/actions.ts b/src/store/modules/util/actions.ts index 5e6203cd0..46284cdd5 100644 --- a/src/store/modules/util/actions.ts +++ b/src/store/modules/util/actions.ts @@ -121,7 +121,96 @@ const actions: ActionTree = { } return statusDesc; - } + }, + + async fetchFacilityTypeInformation({ commit, state }, facilityTypeIds) { + const facilityTypeDesc = JSON.parse(JSON.stringify(state.facilityTypeDesc)) + + const cachedFacilityTypeIds = Object.keys(facilityTypeDesc); + const facilityTypeIdFilter = [...new Set(facilityTypeIds.filter((facilityTypeId: any) => !cachedFacilityTypeIds.includes(facilityTypeId)))] + + // If there are no facility types to fetch skip the API call + if (!facilityTypeIdFilter.length) return; + + const payload = { + inputFields: { + facilityTypeId: facilityTypeIds, + facilityTypeId_op: 'in' + }, + viewSize: facilityTypeIds.length, + entityName: 'FacilityType', + noConditionFind: 'Y', + distinct: "Y", + fieldList: ["facilityTypeId", "description"] + } + + try { + const resp = await UtilService.fetchFacilityTypeInformation(payload); + + if (!hasError(resp) && resp.data?.docs.length > 0) { + resp.data.docs.map((facilityType: any) => { + facilityTypeDesc[facilityType.facilityTypeId] = facilityType['description'] + }) + + commit(types.UTIL_FACILITY_TYPE_UPDATED, facilityTypeDesc) + } else { + throw resp.data; + } + } catch (err) { + console.error('Failed to fetch description for facility types', err) + } + }, + + async fetchPartyInformation({ commit, state }, partyIds) { + let partyInformation = JSON.parse(JSON.stringify(state.partyNames)) + const cachedPartyIds = Object.keys(partyInformation); + const ids = partyIds.filter((partyId: string) => !cachedPartyIds.includes(partyId)) + + if (!ids.length) return partyInformation; + + try { + const payload = { + "inputFields": { + "partyId": ids, + "partyId_op": "in" + }, + "fieldList": ["firstName", "middleName", "lastName", "groupName", "partyId"], + "entityName": "PartyNameView", + "viewSize": ids.length + } + + const resp = await UtilService.fetchPartyInformation(payload); + + if (!hasError(resp)) { + const partyResp = {} as any + resp.data.docs.map((partyInformation: any) => { + + let partyName = '' + if (partyInformation.groupName) { + partyName = partyInformation.groupName + } else { + partyName = [partyInformation.firstName, partyInformation.lastName].join(' ') + } + + partyResp[partyInformation.partyId] = partyName + }) + + partyInformation = { + ...partyInformation, + ...partyResp + } + + commit(types.UTIL_PARTY_NAMES_UPDATED, partyInformation) + } else { + throw resp.data + } + } catch (err) { + console.error('Error fetching party information', err) + } + + return partyInformation; + }, + } export default actions; \ No newline at end of file diff --git a/src/store/modules/util/getters.ts b/src/store/modules/util/getters.ts index 82dae7385..cdd2e3064 100644 --- a/src/store/modules/util/getters.ts +++ b/src/store/modules/util/getters.ts @@ -11,6 +11,12 @@ const getters: GetterTree = { }, getStatusDesc: (state) => (statusId: string) => { return state.statusDesc[statusId] ? state.statusDesc[statusId] : statusId - } + }, + getFacilityTypeDesc: (state) => (facilityTypeId: string) => { + return state.facilityTypeDesc[facilityTypeId] ? state.facilityTypeDesc[facilityTypeId] : '' + }, + getPartyName: (state) => (partyId: string) => { + return state.partyNames[partyId] ? state.partyNames[partyId] : '' + }, } export default getters; \ No newline at end of file diff --git a/src/store/modules/util/index.ts b/src/store/modules/util/index.ts index 36183cfa9..7228f9efc 100644 --- a/src/store/modules/util/index.ts +++ b/src/store/modules/util/index.ts @@ -10,7 +10,9 @@ const utilModule: Module = { state: { rejectReasons: [], paymentMethodTypeDesc: {}, - statusDesc: {} + statusDesc: {}, + facilityTypeDesc: {}, + partyNames: {}, }, getters, actions, diff --git a/src/store/modules/util/mutation-types.ts b/src/store/modules/util/mutation-types.ts index 1d7408f4a..10114d9eb 100644 --- a/src/store/modules/util/mutation-types.ts +++ b/src/store/modules/util/mutation-types.ts @@ -1,4 +1,6 @@ export const SN_UTIL = 'util' export const UTIL_REJECT_REASONS_UPDATED = SN_UTIL + '/REJECT_REASONS_UPDATED' export const UTIL_STATUS_UPDATED = SN_UTIL + '/STATUS_UPDATED' -export const UTIL_PAYMENT_METHODS_UPDATED = SN_UTIL + '/PAYMENT_METHODS_UPDATED' \ No newline at end of file +export const UTIL_PAYMENT_METHODS_UPDATED = SN_UTIL + '/PAYMENT_METHODS_UPDATED' +export const UTIL_FACILITY_TYPE_UPDATED = SN_UTIL + '/FACILITY_TYPE_UPDATED' +export const UTIL_PARTY_NAMES_UPDATED = SN_UTIL + '/PARTY_NAMES_UPDATED' \ No newline at end of file diff --git a/src/store/modules/util/mutations.ts b/src/store/modules/util/mutations.ts index 501ee6a55..d2c275e1c 100644 --- a/src/store/modules/util/mutations.ts +++ b/src/store/modules/util/mutations.ts @@ -11,6 +11,12 @@ const mutations: MutationTree = { }, [types.UTIL_PAYMENT_METHODS_UPDATED] (state, payload) { state.paymentMethodTypeDesc = payload - } + }, + [types.UTIL_FACILITY_TYPE_UPDATED](state, payload) { + state.facilityTypeDesc = payload + }, + [types.UTIL_PARTY_NAMES_UPDATED](state, payload) { + state.partyNames = payload + }, } export default mutations; \ No newline at end of file diff --git a/src/utils/order.ts b/src/utils/order.ts new file mode 100644 index 000000000..bc5486bbc --- /dev/null +++ b/src/utils/order.ts @@ -0,0 +1,97 @@ +const orderCategoryParameters = { + 'Open': { + 'shipmentMethodTypeId': { + 'value': 'STOREPICKUP', + 'OP': 'NOT' + }, + 'shipmentStatusId': { + 'value': '*', + 'OP': 'NOT', + }, + 'fulfillmentStatus': { + 'value': ['Cancelled', 'Rejected'], + 'OP': 'NOT' + }, + 'orderStatusId': { + 'value': 'ORDER_APPROVED' + }, + 'orderTypeId': { + 'value': 'SALES_ORDER' + } + }, + 'Packed': { + 'shipmentMethodTypeId': { + 'value': 'STOREPICKUP', + 'OP': 'NOT' + }, + 'shipmentStatusId': { + 'value': 'SHIPMENT_PACKED', + }, + 'orderTypeId': { + 'value': 'SALES_ORDER' + }, + 'fulfillmentStatus': { + 'value': ['Cancelled', 'Rejected'], + 'OP': 'NOT' + }, + }, + 'Completed': { + 'shipmentMethodTypeId': { + 'value': 'STOREPICKUP', + 'OP': 'NOT' + }, + 'orderItemStatusId': { + 'value': 'ITEM_COMPLETED' + }, + 'orderTypeId': { + 'value': 'SALES_ORDER' + }, + 'docType': { + 'value': 'ORDER' + } + } +} + +const handleParameterMatching = (orderVal: any, parameterVal: any, operation?: string) => { + // considering params will always be an Array for ORing and ANDing + if (operation === 'OR') { + return parameterVal.some((param: any) => orderVal === param) + } else if (operation === 'AND') { + return parameterVal.every((param: any) => orderVal === param) + } else if (operation === 'NOT') { + if(parameterVal === '*') { + if(!orderVal) { + return true; + } + return false; + } + if(Array.isArray(parameterVal)) return !parameterVal.some((param: any) => param === orderVal) + return orderVal !== parameterVal + } else if (!operation) { + return orderVal === parameterVal + } +} + +const getOrderCategory = (order: any) => { + const orderCategoryParameterEntries = Object.entries(orderCategoryParameters) + let result = '' + // using find, as once any of the category is matched then return from here; + orderCategoryParameterEntries.find((entry: any) => { + const [category, parameters] = entry + const paramKeys = Object.keys(parameters) + // used every as to check against each filtering property + + const isMatched = paramKeys.every((key: string) => Object.prototype.hasOwnProperty.call(order, key) && handleParameterMatching(order[key], parameters[key].value, parameters[key]['OP'])) + + // return the value when all params matched for an order + if (isMatched) { + result = category; + return result; + } + }) + return result; +} + +export { + getOrderCategory +} \ No newline at end of file diff --git a/src/utils/solrHelper.ts b/src/utils/solrHelper.ts index 3686292c2..dbf9eaafb 100644 --- a/src/utils/solrHelper.ts +++ b/src/utils/solrHelper.ts @@ -8,7 +8,7 @@ const prepareOrderQuery = (params: any) => { "rows": viewSize, "sort": "orderDate desc", "group": true, - "group.field": "orderId", + "group.field": params.groupBy ? params.groupBy : "orderId", "group.limit": 1000, "group.ngroups": true, "q.op": "AND", @@ -79,6 +79,14 @@ const prepareOrderQuery = (params: any) => { payload.json.filter.push(`orderId: ${params.orderId}`) } + if (params.shipGroupSeqId) { + payload.json.filter.push(`shipGroupSeqId: ${params.shipGroupSeqId}`) + } + + if (params['-shipGroupSeqId']) { + payload.json.filter.push(`-shipGroupSeqId: ${params['-shipGroupSeqId']}`) + } + if(params.orderItemStatusId) { payload.json.filter.push(`orderItemStatusId: ${params.orderItemStatusId}`) } diff --git a/src/views/OrderDetail.vue b/src/views/OrderDetail.vue index a57af815f..3e5ed925a 100644 --- a/src/views/OrderDetail.vue +++ b/src/views/OrderDetail.vue @@ -120,6 +120,56 @@

{{ translate('All order items are rejected') }}

+ + + + + {{ translate("Other shipments in this order") }} + +
+ + +
+ {{ getfacilityTypeDesc(shipGroup.facilityTypeId) }} + {{ shipGroup.facilityName }} + {{ shipGroup.shipGroupSeqId }} +
+ {{ shipGroup.category ? shipGroup.category : '-' }} +
+ + + {{ getPartyName(shipGroup.carrierPartyId) }} + {{ shipGroup.trackingCode }} + + + + + +

{{ translate("Handling Instructions") }}

+

{{ shipGroup.shippingInstructions }}

+
+
+ + + + + + +

{{ getProductIdentificationValue(productIdentificationPref.secondaryId, getProduct(item.productId)) }}

+ {{ getProductIdentificationValue(productIdentificationPref.primaryId, getProduct(item.productId)) ? getProductIdentificationValue(productIdentificationPref.primaryId, getProduct(item.productId)) : getProduct(item.productId).productName }} +

{{ translate("Color:", { color: getFeature(getProduct(item.productId).featureHierarchy, '1/COLOR') }) }}

+

{{ translate("Size:", { size: getFeature(getProduct(item.productId).featureHierarchy, '1/SIZE') }) }}

+
+ + {{ getProductStock(item.productId, item.facilityId).quantityOnHandTotal }} {{ translate('pieces in stock') }} + + + +
+
+
+
+
@@ -140,11 +190,16 @@