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

Implemented: support to fetch data dynamically and added support to add custom field in the download exim(#159) #206

Merged
merged 8 commits into from
Jul 18, 2023
102 changes: 102 additions & 0 deletions src/components/CustomFieldModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<template>
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-button @click="closeModal">
<ion-icon slot="icon-only" :icon="closeOutline" />
</ion-button>
</ion-buttons>
<ion-title>{{ $t("Add custom field") }}</ion-title>
</ion-toolbar>
</ion-header>

<ion-content>
<ion-list>
<ion-item lines="full">
<ion-label position="fixed">{{ $t("Key") }}</ion-label>
<ion-input :placeholder="$t('Enter key')" name="key" v-model="key" id="key" type="text" required />
</ion-item>
<ion-item>
<ion-label position="fixed">{{ $t("Value") }}</ion-label>
<ion-input :placeholder="$t('Enter value')" name="value" v-model="value" id="value" type="text" required />
</ion-item>
</ion-list>

<ion-fab @click="saveCustomField()" vertical="bottom" horizontal="end" slot="fixed">
<ion-fab-button>
<ion-icon :icon="checkmarkDoneOutline" />
</ion-fab-button>
</ion-fab>
</ion-content>
</template>

<script lang="ts">
import {
IonButtons,
IonButton,
IonContent,
IonFab,
IonFabButton,
IonHeader,
IonIcon,
IonInput,
IonItem,
IonLabel,
IonList,
IonTitle,
IonToolbar,
modalController
} from "@ionic/vue";
import { defineComponent } from "vue";
import { checkmarkDoneOutline, closeOutline } from "ionicons/icons";
import { useStore } from "vuex";
import { showToast } from "@/utils"
import { translate } from "@/i18n";

export default defineComponent({
name: "CustomFieldModal",
components: {
IonButtons,
IonButton,
IonContent,
IonFab,
IonFabButton,
IonHeader,
IonIcon,
IonInput,
IonItem,
IonLabel,
IonList,
IonTitle,
IonToolbar,
},
data() {
return {
key: '',
value: ''
}
},
methods: {
closeModal() {
modalController.dismiss({ dismissed: true});
},
saveCustomField() {
const fieldKey = this.key.trim();
if(!fieldKey) {
showToast(translate('Please enter a valid key'))
return;
}
modalController.dismiss({ dismissed: true, value: { key: fieldKey, value: this.value } });
}
},
setup() {
const store = useStore();

return {
checkmarkDoneOutline,
closeOutline,
store
};
},
});
</script>
7 changes: 7 additions & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
" doesn't have any orders in progress right now.": " doesn't have any orders in progress right now.",
" doesn't have any outstanding orders right now.": " doesn't have any outstanding orders right now.",
"Add Box": "Add Box",
"Add custom field": "Add custom field",
"Are you sure you want to change the time zone to?": "Are you sure you want to change the time zone to?",
"Are you sure you want perform this action?": "Are you sure you want perform this action?",
"Are you sure you want to recycle outstanding order(s)?": "Are you sure you want to recycle { ordersCount } outstanding order(s)?",
Expand All @@ -28,14 +29,18 @@
"Download packed orders": "Download packed orders",
"eCom Store": "eCom Store",
"Edit packaging": "Edit packaging",
"Enter key": "Enter key",
"Enter value": "Enter value",
"EXIM": "EXIM",
"Facility ID": "Facility ID",
"Facility updated successfully": "Facility updated successfully",
"Failed to add box": "Failed to add box",
"Failed to create picklist for orders": "Failed to create picklist for orders",
"Failed to generate shipping label": "Failed to generate shipping label",
"Failed to get packed orders information": "Failed to get packed orders information",
"Failed to pack order": "Failed to pack order",
"Failed to pack orders": "Failed to pack orders",
"Failed to parse the data": "Failed to parse the data",
"Failed to print shipping label and packing slip": "Failed to print shipping label and packing slip",
"Failed to ship order": "Failed to ship order",
"Failed to recycle in progress orders": "Failed to recycle in progress orders",
Expand All @@ -52,6 +57,7 @@
"Import shipped order details from an external system based on tracking codes. Orders that have tracking codes will automatically be shipped at the end of the day.": "Import shipped order details from an external system based on tracking codes. Orders that have tracking codes will automatically be shipped at the end of the day.",
"Import shipped orders": "Import shipped orders",
"In Progress": "In Progress",
"Key": "Key",
"Last brokered": "Last brokered",
"Loading": "Loading",
"Login": "Login",
Expand Down Expand Up @@ -162,6 +168,7 @@
"Unpacking this order will send it back to 'In progress' and it will have to be repacked.": "Unpacking this order will send it back to 'In progress' and it will have to be repacked.",
"Upload": "Upload",
"Username": "Username",
"Value": "Value",
"Worn Display": "Worn Display",
"Yes": "Yes",
"You are packing an order. Select additional documents that you would like to print.": "You are packing an order. Select additional documents that you would like to print.",
Expand Down
7 changes: 7 additions & 0 deletions src/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
" doesn't have any orders in progress right now.": " no tiene ningún pedido en curso en este momento.",
" doesn't have any outstanding orders right now.": " no tiene ningún pedido pendiente en este momento.",
"Add Box": "Agregar caja",
"Add custom field": "Add custom field",
"Are you sure you want to change the time zone to?": "¿Estás seguro de que quieres cambiar la zona horaria a?",
"Are you sure you want perform this action?": "¿Estás segura de que quieres realizar esta acción?",
"Are you sure you want to recycle outstanding order(s)?": "¿Está seguro de que desea reciclar # pedido(s) pendiente(s)?",
Expand All @@ -28,14 +29,18 @@
"Download packed orders": "Download packed orders",
"eCom Store": "Tienda electrónica",
"Edit packaging": "Edit packaging",
"Enter key": "Enter key",
"Enter value": "Enter value",
"EXIM": "EXIM",
"Facility ID": "Facility ID",
"Facility updated successfully": "Facility updated successfully",
"Failed to add box": "Failed to add box",
"Failed to create picklist for orders": "Failed to create picklist for orders",
"Failed to generate shipping label": "Failed to generate shipping label",
"Failed to get packed orders information": "Failed to get packed orders information",
"Failed to pack order": "Failed to pack order",
"Failed to pack orders": "Failed to pack orders",
"Failed to parse the data": "Failed to parse the data",
"Failed to print shipping label and packing slip": "Failed to print shipping label and packing slip",
"Failed to ship order": "Failed to ship order",
"Failed to recycle in progress orders": "Failed to recycle in progress orders",
Expand All @@ -52,6 +57,7 @@
"Import shipped order details from an external system based on tracking codes. Orders that have tracking codes will automatically be shipped at the end of the day.": "Import shipped order details from an external system based on tracking codes. Orders that have tracking codes will automatically be shipped at the end of the day.",
"Import shipped orders": "Import shipped orders",
"In Progress": "En curso",
"Key": "Key",
"Last brokered": "Última intermediación",
"Loading": "Loading",
"Login": "Acceso",
Expand Down Expand Up @@ -162,6 +168,7 @@
"Unpacking this order will send it back to 'In progress' and it will have to be repacked.": "Al desbloquear este pedido, se enviará de nuevo a 'En curso' y tendrá que volver a empacarse.",
"Upload": "Upload",
"Username": "Username",
"Value": "Value",
"Worn Display": "Worn Display",
"Yes": "Sí",
"You are packing an order. Select additional documents that you would like to print.": "You are packing an order. Select additional documents that you would like to print.",
Expand Down
10 changes: 10 additions & 0 deletions src/services/UploadService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const uploadJsonFile = async (payload: any): Promise <any> => {
...payload
});
}

const prepareUploadJsonPayload = (request: UploadRequest) => {
const blob = new Blob([JSON.stringify(request.uploadData)], { type: 'application/json'});
const formData = new FormData();
Expand All @@ -26,7 +27,16 @@ const prepareUploadJsonPayload = (request: UploadRequest) => {
}
}

const fetchPackedOrders = async (payload: any): Promise <any> => {
return api({
url: "generateCsvFile",
method: "get",
...payload
});
}

export const UploadService = {
fetchPackedOrders,
prepareUploadJsonPayload,
uploadJsonFile
}
104 changes: 80 additions & 24 deletions src/views/DownloadPackedOrders.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
</ion-header>

<ion-content>
<!-- TODO: remove this option to upload file and make api call in ionViewWillEnter to get the csv file -->
<input :placeholder="$t('Select CSV')" @change="parse" ref="file" class="ion-hide" type="file" id="downloadPackedOrders"/>
<label for="downloadPackedOrders">{{ $t("Upload") }}</label>
<main>
<ion-list>
<ion-list-header>{{ $t("Select the fields you want to include in your export") }}</ion-list-header>
Expand All @@ -28,6 +25,18 @@
</ion-item>
</ion-list>

<ion-list>
<ion-button fill="clear" @click="addCustomField()" :disabled="!Object.keys(fieldMapping).length">{{ $t('Add custom field') }}</ion-button>

<ion-item :key="key" v-for="(value, key) in customFields">
<ion-label>{{ key }}</ion-label>
<ion-label slot="end">{{ value }}</ion-label>
<ion-button slot="end" fill="clear" @click="removeCustomField(key)">
<ion-icon :icon="trashOutline" />
</ion-button>
</ion-item>
</ion-list>

<ion-button size="large" :disabled="!content.length" color="medium" @click="download" expand="block">
{{ $t("Download") }}
</ion-button>
Expand All @@ -40,13 +49,15 @@
<script lang="ts">
import { defineComponent } from 'vue';
import { mapGetters } from "vuex";
import { alertController, IonBackButton, IonButton, IonCheckbox, IonContent, IonHeader, IonIcon, IonItem, IonLabel, IonList, IonListHeader, IonPage, IonTitle, IonToolbar } from '@ionic/vue'
import { pencilOutline } from 'ionicons/icons'
import { alertController, IonBackButton, IonButton, IonCheckbox, IonContent, IonHeader, IonIcon, IonItem, IonLabel, IonList, IonListHeader, IonPage, IonTitle, IonToolbar, modalController } from '@ionic/vue'
import { pencilOutline, trashOutline } from 'ionicons/icons'
import { parseCsv, jsonToCsv, showToast } from '@/utils';
import { translate } from "@/i18n";
import logger from '@/logger';
import { DateTime } from 'luxon';
import { useRouter } from 'vue-router';
import { UploadService } from '@/services/UploadService';
import CustomFieldModal from '@/components/CustomFieldModal.vue'

export default defineComponent({
name: 'UploadImportOrders',
Expand All @@ -67,43 +78,66 @@ export default defineComponent({
},
data() {
return {
file: {} as any,
content: [] as any,
fieldMapping: {} as any,
fileColumns: [] as Array<string>,
dataColumns: [] as Array<string>,
selectedData: {} as any,
isFieldClicked: false
isFieldClicked: false,
customFields: {} as any
}
},
computed: {
...mapGetters({
currentFacility: 'user/getCurrentFacility'
})
},
ionViewDidEnter() {
// TODO: make api call to get the CSV file, instead of upload file option
this.file = {}
async ionViewDidEnter() {
this.content = []
await this.fetchPackedOrders();
},
methods: {
async parse(event: any) {
const file = event.target.files[0];
async fetchPackedOrders() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Add TODO note that timeout need to be set when the order size gets really large

// TODO: need to set a timeout, as when the orders size gets big then the api might take long time to return the information

const payload = {
params: {
configId: 'MDM_PACKED_SHIPMENT',
mimeTypeId: 'application/octet',
facilityId: this.currentFacility.facilityId
}
}

try {
if (file) {
this.content = await parseCsv(file).then(res => res);
// get the column names from the file
this.fileColumns = Object.keys(this.content[0]);
// generate default mappings for the columns
this.fieldMapping = this.fileColumns.reduce((fieldMapping: any, field: string) => {
fieldMapping[field] = field
return fieldMapping;
}, {})
const resp = await UploadService.fetchPackedOrders(payload);

if(resp.status == 200 && resp.data) {
await this.parse(resp.data)
} else {
logger.error("No file upload. Please try again");
throw resp.data
}
} catch (err) {
showToast(translate('Failed to get packed orders information'))
logger.error('Failed to get packed orders', err)
}
},
async parse(data: any) {
try {
this.content = await parseCsv(data).then(res => res);
// get the column names from the data
this.dataColumns = Object.keys(this.content[0]);
// generate default mappings for the columns
this.fieldMapping = this.dataColumns.reduce((fieldMapping: any, field: string) => {
// check to not add the field for which the key is not available, as when fetching the data we are getting an empty key
if(!field) {
return fieldMapping;
}

fieldMapping[field] = field
return fieldMapping;
}, {})
} catch {
this.content = []
logger.error("Please upload a valid csv to continue");
logger.error("Failed to parse the data");
}
},
async addCustomLabel(field: any) {
Expand Down Expand Up @@ -166,6 +200,11 @@ export default defineComponent({
}, {}))
})

// adding custom fields in the data
Object.keys(this.customFields).map((field: any) => {
downloadData.map((data: any) => data[field] = this.customFields[field])
})

const alert = await alertController.create({
header: this.$t("Download packed orders"),
message: this.$t("Make sure all the labels provided are correct."),
Expand All @@ -185,13 +224,30 @@ export default defineComponent({
},
selectAll() {
this.selectedData = JSON.parse(JSON.stringify(this.fieldMapping))
},
async addCustomField() {
const customFieldModal = await modalController.create({
component: CustomFieldModal
});

customFieldModal.onDidDismiss().then((result) => {
if(result.data && result.data.value) {
this.customFields[result.data.value.key] = result.data.value.value
}
})

return customFieldModal.present();
},
removeCustomField(key: any) {
delete this.customFields[key];
}
},
setup() {
const router = useRouter();

return {
pencilOutline,
trashOutline,
router
}
}
Expand Down
Loading