Skip to content

Commit

Permalink
Merge pull request #526 from hotwax/#525
Browse files Browse the repository at this point in the history
Improved: Added support to show errors for the erroneous uploads and to view the uploaded file (#525).
  • Loading branch information
ravilodhi authored Dec 4, 2024
2 parents a26fdaa + 5c20cd1 commit 9ba90a3
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 27 deletions.
88 changes: 88 additions & 0 deletions src/components/BulkUploadErrorModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<template>
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button @click="closeModal">
<ion-icon :icon="close" />
</ion-button>
</ion-buttons>
<ion-title>{{ translate("Import Error") }}</ion-title>
</ion-toolbar>
</ion-header>

<ion-content>
<ion-list>
<ion-item lines="full">
<ion-icon :icon="bookOutline" slot="start"/>
{{ translate("View upload guide") }}
<ion-button color="medium" fill="clear" slot="end" @click="viewUploadGuide">
<ion-icon slot="icon-only" :icon="openOutline"/>
</ion-button>
</ion-item>
<ion-item lines="none">
<ion-label class="ion-text-wrap">
<template v-if="systemMessageError.errorText">
{{ systemMessageError.errorText }}
</template>
<template v-else>
{{ translate("No data found") }}
</template>
</ion-label>
</ion-item>
</ion-list>
</ion-content>
</template>

<script setup>
import {
IonButtons,
IonButton,
IonContent,
IonHeader,
IonIcon,
IonItem,
IonLabel,
IonTitle,
IonToolbar,
IonList,
modalController
} from '@ionic/vue';
import { bookOutline, close, openOutline } from "ionicons/icons";
import { useStore } from "vuex";
import { defineProps, onMounted, ref } from 'vue';
import { translate } from "@hotwax/dxp-components";
import { CountService } from "@/services/CountService"
import { hasError } from "@/utils";
import logger from "@/logger";
const store = useStore();

Check warning on line 59 in src/components/BulkUploadErrorModal.vue

View workflow job for this annotation

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

'store' is assigned a value but never used

Check warning on line 59 in src/components/BulkUploadErrorModal.vue

View workflow job for this annotation

GitHub Actions / call-workflow-in-another-repo / build_and_deploy

'store' is assigned a value but never used

Check warning on line 59 in src/components/BulkUploadErrorModal.vue

View workflow job for this annotation

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

'store' is assigned a value but never used
let systemMessageError = ref({})
const props = defineProps(["systemMessage"])
onMounted(async() => {
await fetchCycleCountImportErrors()
})
function viewUploadGuide() {
window.open('https://docs.hotwax.co/documents/retail-operations/inventory/introduction/draft-cycle-count', '_blank');
}
function closeModal() {
modalController.dismiss({ dismissed: true });
}
async function fetchCycleCountImportErrors () {
try {
const resp = await CountService.fetchCycleCountImportErrors({
systemMessageId: props.systemMessage.systemMessageId,
});
if (!hasError(resp)) {
systemMessageError.value = resp?.data[0]
} else {
throw resp.data;
}
} catch (err) {
logger.error(err);
}
}
</script>
90 changes: 90 additions & 0 deletions src/components/CycleCountUploadActionPopover.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<template>
<ion-content>
<ion-list>
<ion-list-header>{{ systemMessage.systemMessageId }}</ion-list-header>
<ion-item v-if="systemMessage.statusId === 'SmsgReceived'" lines="none" button @click="cancelUpload()">
<ion-icon slot="end" />
{{ translate("Cancel") }}
</ion-item>
<ion-item v-if="systemMessage.statusId === 'SmsgError'" lines="none" button @click="viewErrorModal()">
<ion-icon slot="end"/>
{{ translate("View error") }}
</ion-item>
<ion-item lines="none" button @click="viewFile()">
<ion-icon slot="end"/>
{{ translate("View file") }}
</ion-item>
</ion-list>
</ion-content>
</template>

<script setup lang="ts">
import {
IonContent,
IonIcon,
IonItem,
IonList,
IonListHeader,
modalController,
popoverController
} from "@ionic/vue"
import { defineProps } from "vue";
import { translate } from "@/i18n"
import store from "@/store";
import { CountService } from "@/services/CountService"
import { downloadCsv, hasError, showToast } from "@/utils";
import logger from "@/logger";
import BulkUploadErrorModal from "./BulkUploadErrorModal.vue";
const props = defineProps(["systemMessage", "fileName"])
function closePopover() {
popoverController.dismiss()
}
async function viewFile() {
try {
const resp = await CountService.fetchCycleCountUploadedFileData({
systemMessageId: props.systemMessage.systemMessageId,
});
if (!hasError(resp)) {
downloadCsv(resp.data.csvData, props.fileName)
} else {
throw resp.data;
}
} catch (err) {
showToast(translate('Failed to download uploaded cycle count file.'))
logger.error(err);
}
closePopover()
}
async function viewErrorModal() {
const bulkUploadErrorModal = await modalController.create({
component: BulkUploadErrorModal,
componentProps: { systemMessage: props.systemMessage }
});
// dismissing the popover once the picker modal is closed
bulkUploadErrorModal.onDidDismiss().finally(() => {
closePopover();
});
return bulkUploadErrorModal.present();
}
async function cancelUpload () {
try {
const resp = await CountService.cancelCycleCountFileProcessing({
systemMessageId: props.systemMessage.systemMessageId,
statusId: 'SmsgCancelled'
});
if (!hasError(resp)) {
showToast(translate('Cycle count cancelled successfully.'))
await store.dispatch('count/fetchCycleCountImportSystemMessages')
} else {
throw resp.data;
}
} catch (err) {
showToast(translate('Failed to cancel uploaded cycle count.'))
logger.error(err);
}
closePopover()
}
</script>
5 changes: 5 additions & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
"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.",
"Import CSV": "Import CSV",
"Import Error": "Import Error",
"In stock": "In stock",
"inventory variance": "inventory variance",
"Instance Url": "Instance Url",
Expand Down Expand Up @@ -154,6 +155,7 @@
"New Count": "New Count",
"New mapping": "New mapping",
"No cycle counts found": "No cycle counts found",
"No data found": "No data found",
"No items found": "No items found",
"No new file upload. Please try again.": "No new file upload. Please try again.",
"No products found.": "No products found.",
Expand Down Expand Up @@ -297,6 +299,9 @@
"Variance updated successfully": "Variance updated successfully",
"Version:": "Version: {appVersion}",
"View": "View",
"View error": "View error",
"View file": "View file",
"View upload guide": "View upload guide",
"You do not have permission to access the app.": "You do not have permission to access the app.",
"You do not have permission to access this page": "You do not have permission to access this page",
"3 rejections in the last week": "3 rejections in the last week"
Expand Down
18 changes: 18 additions & 0 deletions src/services/CountService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,22 @@ const cancelCycleCountFileProcessing = async (payload: any): Promise <any> => {
});
}

const fetchCycleCountImportErrors = async (payload: any): Promise <any> => {
return api({
url: `cycleCounts/systemMessages/${payload.systemMessageId}/errors`,
method: "get",
data: payload
});
}

const fetchCycleCountUploadedFileData = async (payload: any): Promise <any> => {
return api({
url: `cycleCounts/systemMessages/${payload.systemMessageId}/downloadFile`,
method: "get",
data: payload
});
}

export const CountService = {
acceptItem,
addProductToCount,
Expand All @@ -145,7 +161,9 @@ export const CountService = {
createCycleCount,
deleteCycleCountItem,
fetchBulkCycleCountItems,
fetchCycleCountImportErrors,
fetchCycleCountImportSystemMessages,
fetchCycleCountUploadedFileData,
fetchCycleCount,
fetchCycleCountStats,
fetchCycleCounts,
Expand Down
6 changes: 3 additions & 3 deletions src/store/modules/count/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,12 +187,12 @@ const actions: ActionTree<CountState, RootState> = {
async fetchCycleCountImportSystemMessages({commit} ,payload) {

Check warning on line 187 in src/store/modules/count/actions.ts

View workflow job for this annotation

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

'payload' is defined but never used

Check warning on line 187 in src/store/modules/count/actions.ts

View workflow job for this annotation

GitHub Actions / call-workflow-in-another-repo / build_and_deploy

'payload' is defined but never used

Check warning on line 187 in src/store/modules/count/actions.ts

View workflow job for this annotation

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

'payload' is defined but never used
let systemMessages;
try {
const fifteenMinutesEarlier = DateTime.now().minus({ minutes: 15 });
const twentyFourHoursEarlier = DateTime.now().minus({ hours: 24 });
const resp = await CountService.fetchCycleCountImportSystemMessages({
systemMessageTypeId: "ImportInventoryCounts",
initDate_from: fifteenMinutesEarlier.toMillis(),
initDate_from: twentyFourHoursEarlier.toMillis(),
orderByField: 'initDate desc, processedDate desc',
pageSize: 10
pageSize: 100
})
if (!hasError(resp)) {
systemMessages = resp.data
Expand Down
9 changes: 8 additions & 1 deletion src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,11 @@ const jsonToCsv = (file: any, options: JsonToCsvOption = {}) => {
return blob;
};

export { jsonToCsv, showToast, hasError, handleDateTimeInput, getCycleCountStats, getDateTime, getDateWithOrdinalSuffix, getDerivedStatusForCount, getFacilityName, getPartyName, getProductIdentificationValue, timeFromNow, parseCsv }
const downloadCsv = (csv: any, fileName: any) => {
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
saveAs(blob, fileName ? fileName : "CycleCountImport.csv");

return blob;
};

export { downloadCsv, jsonToCsv, showToast, hasError, handleDateTimeInput, getCycleCountStats, getDateTime, getDateWithOrdinalSuffix, getDerivedStatusForCount, getFacilityName, getPartyName, getProductIdentificationValue, timeFromNow, parseCsv }
44 changes: 21 additions & 23 deletions src/views/BulkUpload.vue
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@
</ion-label>
<div class="system-message-action">
<ion-note slot="end">{{ getFileProcessingStatus(systemMessage) }}</ion-note>
<ion-button :disabled="systemMessage.statusId !== 'SmsgReceived'" slot="end" fill="clear" color="medium" @click="cancelUpload(systemMessage)">
<ion-icon slot="icon-only" :icon="trashBinOutline" />
<ion-button slot="end" fill="clear" color="medium" @click="openUploadActionPopover($event, systemMessage)">
<ion-icon slot="icon-only" :icon="ellipsisVerticalOutline" />
</ion-button>
</div>
</ion-item>
Expand Down Expand Up @@ -105,20 +105,19 @@ import {
IonToolbar,
onIonViewDidEnter,
alertController,
modalController
modalController,
popoverController
} from '@ionic/vue';
import { addOutline, cloudUploadOutline, trashBinOutline } from "ionicons/icons";
import { addOutline, cloudUploadOutline, ellipsisVerticalOutline, trashBinOutline } from "ionicons/icons";
import { translate } from '@/i18n';
import { computed, ref } from "vue";
import { useStore } from 'vuex';
import { useRouter } from 'vue-router'
import { hasError, jsonToCsv, parseCsv, showToast } from "@/utils";
import CreateMappingModal from "@/components/CreateMappingModal.vue";
import { CountService } from "@/services/CountService"
import logger from "@/logger";
import CycleCountUploadActionPopover from "@/components/CycleCountUploadActionPopover.vue"
const store = useStore();
const router = useRouter()
const fieldMappings = computed(() => store.getters["user/getFieldMappings"])
const systemMessages = computed(() => store.getters["count/getCycleCountImportSystemMessages"])
Expand Down Expand Up @@ -187,26 +186,25 @@ function getFileProcessingStatus(systemMessage) {
processingStatus = "processing"
} else if (systemMessage.statusId === 'SmsgCancelled') {
processingStatus = 'cancelled'
} else if (systemMessage.statusId === 'SmsgError') {
processingStatus = 'error'
}
return processingStatus;
}
async function cancelUpload (systemMessage) {
try {
const resp = await CountService.cancelCycleCountFileProcessing({
systemMessageId: systemMessage.systemMessageId,
statusId: 'SmsgCancelled'
});
if (!hasError(resp)) {
showToast(translate('Cycle count cancelled successfully.'))
await store.dispatch('count/fetchCycleCountImportSystemMessages')
} else {
throw resp.data;
}
} catch (err) {
showToast(translate('Failed to cancel uploaded cycle count.'))
logger.error(err);
}
async function openUploadActionPopover(event, systemMessage){
const popover = await popoverController.create({
component: CycleCountUploadActionPopover,
event,
componentProps: {
systemMessage,
fileName: extractFilename(systemMessage.messageText)
},
showBackdrop: false,
});
await popover.present();
}
async function parse(event) {
const file = event.target.files[0];
try {
Expand Down

0 comments on commit 9ba90a3

Please sign in to comment.