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: Added support to show errors for the erroneous uploads and to view the uploaded file (#525). #526

Merged
merged 4 commits into from
Dec 4, 2024
Merged
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
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 / 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 @@ -184,15 +184,15 @@
commit(types.COUNT_ITEMS_UPDATED, [])
},

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 / 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
Loading