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: Unified inventory upload(#304) #309

Merged
merged 14 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from 8 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
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ VUE_APP_ALIAS={}
VUE_APP_MAPPING_TYPES={"PO": "PO_MAPPING_PREF","RSTINV": "INV_MAPPING_PREF","RSTSTK": "STK_MAPPING_PREF"}
VUE_APP_MAPPING_PO={"orderId": { "label": "Order ID", "required": true }, "productSku": { "label": "Shopify product SKU", "required": true },"orderDate": { "label": "Arrival date", "required": true }, "quantity": { "label": "Ordered quantity", "required": true }, "facility": { "label": "Facility ID", "required": true }}
VUE_APP_MAPPING_RSTINV={"productIdentification": { "label": "Product Identification", "required": true }, "quantity": { "label": "Quantity", "required": true }, "facility": { "label": "Facility ID", "required": true }}
VUE_APP_MAPPING_RSTSTK={"productIdentification": { "label": "Product Identification", "required": true }, "restockQuantity": { "label": "Restock quantity", "required": true }}
VUE_APP_MAPPING_RSTSTK={"productIdentification": { "label": "Product Identification", "required": true }, "facility": { "label": "Facility", "required": true }, "quantity": { "label": "Quantity", "required": true }}
VUE_APP_MAPPING_ADJINV={"productIdentification": { "label": "Product Identification", "required": true }, "facility": { "label": "Facility", "required": true }, "quantity": { "label": "Quantity", "required": true }}
VUE_APP_DEFAULT_LOG_LEVEL="error"
VUE_APP_LOGIN_URL="http://launchpad.hotwax.io/login"
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
207 changes: 207 additions & 0 deletions src/components/AddProductModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
<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>{{ translate("Add product") }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content ref="contentRef" :scroll-events="true" @ionScroll="enableScrolling()">
<ion-searchbar v-model="queryString" :placeholder="translate('Search SKU or product name')" @keyup.enter="handleSearch" @ionInput="handleInput"/>

<template v-if="products.length">
<ion-list v-for="product in products" :key="product.productId">
<ion-item lines="none">
<ion-thumbnail slot="start">
<Image :src="product.mainImageUrl" />
</ion-thumbnail>
<ion-label>
<h2>{{ getProductIdentificationValue(productIdentificationPref.primaryId, getProductById(product.productId)) ? getProductIdentificationValue(productIdentificationPref.primaryId, getProductById(product.productId)) : getProductById(product.productId).productName }}</h2>
<p>{{ getProductIdentificationValue(productIdentificationPref.secondaryId, getProductById(product.productId)) }}</p>
</ion-label>
<ion-icon v-if="isProductAvailableInShipment(product.productId)" color="success" :icon="checkmarkCircle" />
<ion-button v-else fill="outline" @click="addToShipment(product.productId)">{{ translate("Add to shipment") }}</ion-button>
</ion-item>
</ion-list>

<ion-infinite-scroll @ionInfinite="loadMoreProducts($event)" threshold="100px" v-show="isScrollable" ref="infiniteScrollRef">
<ion-infinite-scroll-content loading-spinner="crescent" :loading-text="translate('Loading')" />
</ion-infinite-scroll>
</template>

<div v-else-if="queryString && isSearching && !products.length" class="empty-state">
<p>{{ translate("No product found") }}</p>
</div>
<div v-else class="empty-state">
<img src="../assets/images/empty-state-add-product-modal.png" alt="empty-state" />
<p>{{ translate("Enter a SKU, or product name to search a product") }}</p>
</div>
</ion-content>
</template>

<script>
import {
IonButton,
IonButtons,
IonContent,
IonHeader,
IonIcon,
IonInfiniteScroll,
IonItem,
IonLabel,
IonList,
IonSearchbar,
IonThumbnail,
IonTitle,
IonToolbar,
modalController,
} from "@ionic/vue";
import { closeOutline, checkmarkCircle } from "ionicons/icons"
import { defineComponent, computed } from "vue"
import { mapGetters, useStore } from "vuex";
import { useRouter } from "vue-router"
import store from "@/store"
import { translate, getProductIdentificationValue, useProductIdentificationStore } from "@hotwax/dxp-components";
import Image from "@/components/Image.vue"
import { UtilService } from "@/services/UtilService";
import { hasError } from "@/adapter";
import { showToast } from '@/utils';
import logger from "@/logger";

export default defineComponent({
name: "AddProductModal",
components: {
IonButton,
IonButtons,
IonContent,
IonHeader,
IonIcon,
IonInfiniteScroll,
IonItem,
IonLabel,
IonList,
IonSearchbar,
IonThumbnail,
IonTitle,
IonToolbar,
Image,
},
data() {
return {
queryString: '',
isSearching: false,
isScrollingEnabled: false
}
},
async ionViewWillEnter() {
this.isScrollingEnabled = false;
},
unmounted() {
store.dispatch("product/clearProducts");
},
props: ["shipmentId"],
computed: {
...mapGetters({
products: 'product/getProducts',
isScrollable: 'product/isScrollable',
getProductById: 'product/getProductById',
isProductAvailableInShipment: 'product/isProductAvailableInShipment',
})
},
methods: {
enableScrolling() {
const parentElement = this.$refs.contentRef.$el
const scrollEl = parentElement.shadowRoot.querySelector("main[part='scroll']")
let scrollHeight = scrollEl.scrollHeight, infiniteHeight = this.$refs.infiniteScrollRef.$el.offsetHeight, scrollTop = scrollEl.scrollTop, threshold = 100, height = scrollEl.offsetHeight
const distanceFromInfinite = scrollHeight - infiniteHeight - scrollTop - threshold - height
if(distanceFromInfinite < 0) {
this.isScrollingEnabled = false;
} else {
this.isScrollingEnabled = true;
}
},
async handleSearch() {
if (!this.queryString) {
this.isSearching = false;
store.dispatch("product/clearProducts");
return;
}
await this.getProducts();
this.isSearching = true;
},
async getProducts( vSize, vIndex) {

const viewSize = vSize ? vSize : process.env.VUE_APP_VIEW_SIZE;
const viewIndex = vIndex ? vIndex : 0;
const payload = {
viewSize,
viewIndex,
queryString: this.queryString
}
if (this.queryString) {
await this.store.dispatch("product/findProduct", payload);
}
},
async loadMoreProducts(event) {
if(!(this.isScrollingEnabled && this.isScrollable)) {
await event.target.complete();
}
this.getProducts(
undefined,
Math.ceil(this.products.length / process.env.VUE_APP_VIEW_SIZE).toString()
).then(() => {
event.target.complete();
})
},
async addToShipment(productId) {
let resp;
const payload = {
productId: productId,
shipmentId: this.shipmentId,
quantity: 1
}
try {
resp = await UtilService.addProductToShipment(payload)
if (!hasError(resp)) {
showToast(translate("Product added successfully"));
await this.store.dispatch('util/fetchShipmentItems', { shipmentId: this.shipmentId });
} else {
throw resp.data;
}
} catch (err) {
showToast(translate("Failed to add product to shipment"))
logger.error(err)
}
},
closeModal() {
modalController.dismiss({ dismissed: true });
},
handleInput() {
if (!this.queryString) {
this.isSearching = false;
store.dispatch("product/clearProducts");
}
},
},

setup() {
const store = useStore();
const router = useRouter();
const productIdentificationStore = useProductIdentificationStore();
let productIdentificationPref = computed(() => productIdentificationStore.getProductIdentificationPref);

return {
closeOutline,
checkmarkCircle,
translate,
store,
getProductIdentificationValue,
productIdentificationPref,
router
}
}
})
</script>
78 changes: 78 additions & 0 deletions src/components/DownloadLogsFilePopover.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<template>
<ion-content>
<ion-list>
<ion-list-header>{{ dataManagerLog.logId }}</ion-list-header>
<ion-item button @click="downloadFile('logFile')">
<ion-label>{{ translate("Log file") }}</ion-label>
</ion-item>
<ion-item button @click="downloadFile('uploadedFile')">
<ion-label>{{ translate("Uploaded file") }}</ion-label>
</ion-item>
<ion-item button :disabled="!dataManagerLog?.errorRecordContentId" lines="none" @click="downloadFile('failedRecords')">
<ion-label>{{ translate("Failed records") }}</ion-label>
</ion-item>
</ion-list>
</ion-content>
</template>

<script lang="ts">
import {
IonContent,
IonItem,
IonLabel,
IonList,
IonListHeader,
popoverController
} from "@ionic/vue";
import { defineComponent } from "vue";
import { translate } from "@hotwax/dxp-components";
import { UtilService } from "@/services/UtilService";
import { saveDataFile, showToast } from '@/utils';
import logger from "@/logger";

export default defineComponent({
name: "DownloadLogsFilePopover",
components: {
IonContent,
IonItem,
IonLabel,
IonList,
IonListHeader
},
props: ["dataManagerLog"],
methods: {
async downloadFile(type: string) {
let dataResource = {} as any;

if (type === 'logFile') {
dataResource.dataResourceId = this.dataManagerLog.logFileDataResourceId
dataResource.name = this.dataManagerLog.logFileContentName
} else if (type === 'uploadedFile') {
dataResource.name = this.dataManagerLog.contentName
dataResource.dataResourceId = this.dataManagerLog.dataResourceId
} else if (type === 'failedRecords') {
dataResource.dataResourceId = this.dataManagerLog.errorRecordDataResourceId
dataResource.name = this.dataManagerLog.errorRecordContentName
}

if (dataResource.dataResourceId) {
try {
const response = await UtilService.fetchFileData({
dataResourceId: dataResource.dataResourceId
});
saveDataFile(response.data, dataResource.name);
} catch (error) {
showToast(translate("Error downloading file"))
logger.error(error)
}
}
popoverController.dismiss();
}
},
setup() {
return {
translate
}
}
});
</script>
18 changes: 2 additions & 16 deletions src/components/Menu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -86,25 +86,11 @@ export default defineComponent({
const appPages = [
{
title: "Inventory",
url: "/inventory",
childRoutes: ["/inventory-review"],
url: "/unified-inventory",
childRoutes: ["/inventory", "/inventory-review", "/adjust-inventory", "/adjust-inventory_history", "/scheduled-incoming-inventory", "/scheduled-restock", "/scheduled-restock-review/:id", "/purchase-order", "/purchase-order-review"],
iosIcon: albumsOutline,
mdIcon: albumsOutline
},
{
title: "Scheduled restock",
url: "/scheduled-restock",
childRoutes: ["/scheduled-restock-review"],
iosIcon: timerOutline,
mdIcon: timerOutline
},
{
title: "Purchase order",
url: "/purchase-order",
childRoutes: ["/purchase-order-review"],
iosIcon: calendar,
mdIcon: calendar
},
{
title: "Saved Mappings",
url: "/saved-mappings",
Expand Down
17 changes: 14 additions & 3 deletions src/components/ScheduledRestockPopover.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@
</ion-content>
</ion-modal>
</ion-item>
<ion-item button @click="cancelJob()" lines="none">
<ion-item button @click="cancelJob">
{{ translate("Cancel") }}
</ion-item>
<ion-item button @click="reviewJobItems" lines="none">
{{ translate("View details") }}
</ion-item>
</ion-list>
</ion-content>
</template>
Expand All @@ -35,12 +38,13 @@ import {
IonItem,
IonList,
IonListHeader,
IonModal
IonModal,
popoverController
} from "@ionic/vue";
import { defineComponent } from "vue";
import { useStore, mapGetters } from "vuex";
import { useRouter } from "vue-router";
import { DateTime } from 'luxon';
import { popoverController } from "@ionic/core";

export default defineComponent({
name: "ScheduledRestockPopover",
Expand All @@ -65,6 +69,10 @@ export default defineComponent({
},
props: ["job"],
methods: {
reviewJobItems() {
popoverController.dismiss()
this.router.push({ name: 'ScheduledRestockReview', params: { id: this.job.jobId } });
},
async cancelJob() {
let resp;
try {
Expand Down Expand Up @@ -138,8 +146,11 @@ export default defineComponent({
},
setup() {
const store = useStore();
const router = useRouter();

return {
store,
router,
translate
}
},
Expand Down
Loading
Loading