-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Implemented: NetSuite Integration Management UI(#37)
- Loading branch information
Showing
50 changed files
with
2,652 additions
and
119 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
<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("Discounts") }}</ion-title> | ||
</ion-toolbar> | ||
</ion-header> | ||
|
||
<ion-content> | ||
<ion-item class="ion-margin-top"> | ||
<ion-icon slot="start" :icon="informationCircleOutline" /> | ||
<ion-label> | ||
{{ translate("Learn more about discounts in NetSuite") }} | ||
</ion-label> | ||
<ion-button fill="clear" size="small" color="medium"> | ||
<ion-icon :icon="openOutline" slot="icon-only" /> | ||
</ion-button> | ||
</ion-item> | ||
|
||
<ion-item lines="full" class="ion-margin-top"> | ||
<ion-input v-model="orderLevelDiscount" :label="translate('Order level discount')" :placeholder="translate('NetSuite discount item ID')" /> | ||
</ion-item> | ||
|
||
<ion-item lines="full"> | ||
<ion-input v-model="itemLevelDiscount" :label="translate('Item level discounts')" :placeholder="translate('NetSuite discount item ID')" /> | ||
</ion-item> | ||
|
||
<ion-fab vertical="bottom" horizontal="end" slot="fixed"> | ||
<ion-fab-button @click="editNetSuiteDiscountItemIds" :disabled="isDiscountValueChanged()"> | ||
<ion-icon :icon="saveOutline" /> | ||
</ion-fab-button> | ||
</ion-fab> | ||
</ion-content> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import { IonButton, IonButtons, IonContent, IonFab, IonFabButton, IonHeader, IonIcon, IonInput, IonItem, IonLabel, IonTitle, IonToolbar, modalController } from "@ionic/vue"; | ||
import { closeOutline, informationCircleOutline, openOutline, saveOutline } from 'ionicons/icons'; | ||
import { translate } from "@/i18n" | ||
import { useStore } from "vuex"; | ||
import { computed, onMounted, ref } from "vue"; | ||
import { useNetSuiteComposables } from "@/composables/useNetSuiteComposables"; | ||
const store = useStore(); | ||
const discountTypeId = JSON.parse(process.env.VUE_APP_NETSUITE_INTEGRATION_TYPE_MAPPING)?.DISCOUNT_TYPE_ID | ||
const { addNetSuiteId, updateNetSuiteId } = useNetSuiteComposables(discountTypeId) | ||
const integrationTypeMappings = computed(() => store.getters["netSuite/getIntegrationTypeMappings"](discountTypeId)) | ||
const orderLevelDiscount = ref(""); | ||
const itemLevelDiscount = ref(""); | ||
const integrationMappingByKey = ref({}) as any | ||
const mappingKeys = { | ||
order: "SHOPIFY_DISC", | ||
item: "SHOPIFY_ITEM_DISC" | ||
} | ||
onMounted(async () => { | ||
// Set orderLevelDiscount and itemLevelDiscount based on their corresponding mapping keys in integration type mappings. | ||
integrationTypeMappings.value.map((mapping: any) => { | ||
integrationMappingByKey[mapping.mappingKey] = mapping | ||
if(mapping.mappingKey === mappingKeys.order) { | ||
orderLevelDiscount.value = mapping.mappingValue | ||
} else { | ||
itemLevelDiscount.value = mapping.mappingValue | ||
} | ||
}); | ||
}) | ||
function closeModal() { | ||
modalController.dismiss({ dismissed: true }); | ||
} | ||
function isDiscountValueChanged() { | ||
return !(orderLevelDiscount.value?.trim() && itemLevelDiscount.value?.trim() && (orderLevelDiscount.value !== integrationMappingByKey[mappingKeys.order]?.mappingValue || itemLevelDiscount.value !== integrationMappingByKey[mappingKeys.item]?.mappingValue)); | ||
} | ||
async function editNetSuiteDiscountItemIds() { | ||
if(orderLevelDiscount.value !== integrationMappingByKey[mappingKeys.order].mappingValue) { | ||
await updateMapping(mappingKeys.order, orderLevelDiscount.value) | ||
} | ||
if(!itemLevelDiscount.value !== integrationMappingByKey[mappingKeys.item].mappingValue) { | ||
await updateMapping(mappingKeys.item, itemLevelDiscount.value) | ||
} | ||
closeModal(); | ||
} | ||
async function updateMapping(mappingKey: any, mappingValue: any) { | ||
const payload = { | ||
integrationTypeId: discountTypeId, | ||
mappingKey, | ||
mappingValue | ||
} | ||
if(integrationMappingByKey[mappingKey]?.integrationMappingId) { | ||
await updateNetSuiteId(payload, integrationMappingByKey[mappingKey].integrationMappingId); | ||
} else { | ||
await addNetSuiteId(payload); | ||
} | ||
} | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
<template> | ||
<ion-menu content-id="main-content" type="overlay" :disabled="!isUserAuthenticated"> | ||
<ion-header> | ||
<ion-toolbar> | ||
<ion-title>{{ translate("Company") }}</ion-title> | ||
</ion-toolbar> | ||
</ion-header> | ||
|
||
<ion-content> | ||
<ion-list id="company-list"> | ||
<ion-menu-toggle auto-hide="false" v-for="(p, i) in appPages" :key="i"> | ||
<ion-item button router-direction="root" :router-link="p.url" class="hydrated" :class="{ selected: selectedIndex === i }"> | ||
<ion-icon slot="start" :ios="p.iosIcon" :md="p.mdIcon" /> | ||
<ion-label>{{ p.title }}</ion-label> | ||
</ion-item> | ||
</ion-menu-toggle> | ||
</ion-list> | ||
</ion-content> | ||
</ion-menu> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import { | ||
IonContent, | ||
IonIcon, | ||
IonHeader, | ||
IonItem, | ||
IonLabel, | ||
IonList, | ||
IonTitle, | ||
IonToolbar, | ||
IonMenu, | ||
IonMenuToggle, | ||
} from "@ionic/vue"; | ||
import { computed } from "vue"; | ||
import { businessOutline, settingsOutline, walletOutline } from "ionicons/icons"; | ||
import { useStore } from "@/store"; | ||
import { useRouter } from "vue-router"; | ||
import { translate } from "@/i18n"; | ||
const store = useStore(); | ||
const router = useRouter(); | ||
const appPages = [ | ||
{ | ||
title: "Product Store", | ||
url: "/product-store", | ||
childRoutes: ["/product-store/"], | ||
iosIcon: businessOutline, | ||
mdIcon: businessOutline, | ||
}, | ||
// { | ||
// title: "Shopify", | ||
// url: "/shopify", | ||
// childRoutes: ["/shopify/"], | ||
// iosIcon: cartOutline, | ||
// mdIcon: cartOutline, | ||
// }, | ||
{ | ||
title: "NetSuite", | ||
url: "/netsuite", | ||
childRoutes: ["/netsuite/"], | ||
iosIcon: walletOutline, | ||
mdIcon: walletOutline | ||
}, | ||
{ | ||
title: "Settings", | ||
url: "/settings", | ||
iosIcon: settingsOutline, | ||
mdIcon: settingsOutline, | ||
} | ||
]; | ||
const isUserAuthenticated = computed(() => store.getters["user/isUserAuthenticated"]) | ||
const selectedIndex = computed(() => { | ||
const path = router.currentRoute.value.path | ||
return appPages.findIndex((screen) => screen.url === path || screen.childRoutes?.includes(path) || screen.childRoutes?.some((route) => path.includes(route))) | ||
}) | ||
</script> | ||
|
||
<style scoped> | ||
ion-item.selected ion-icon { | ||
color: var(--ion-color-secondary); | ||
} | ||
ion-item.selected { | ||
--color: var(--ion-color-secondary); | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
<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("Price level") }}</ion-title> | ||
</ion-toolbar> | ||
</ion-header> | ||
|
||
<ion-content> | ||
<ion-item class="ion-margin-top"> | ||
<ion-icon slot="start" :icon="informationCircleOutline" /> | ||
<ion-label> | ||
{{ translate("Learn more about price levels in NetSuite") }} | ||
</ion-label> | ||
<ion-button fill="clear" size="small" color="medium" @click="openPriceLevelDoc"> | ||
<ion-icon :icon="openOutline" slot="icon-only" /> | ||
</ion-button> | ||
</ion-item> | ||
|
||
<ion-item lines="full" class="ion-margin-top"> | ||
<ion-input v-model="selectedPriceLevel" :label="translate('Price level')" :placeholder="translate('Base Price')"/> | ||
</ion-item> | ||
|
||
<ion-list> | ||
<ion-list-header>{{ translate("Frequently used") }}</ion-list-header> | ||
<ion-radio-group v-model="selectedPriceLevel"> | ||
<ion-item> | ||
<ion-radio value="Base" label-placement="end" justify="start"> | ||
<ion-label> | ||
{{ translate("Base Price") }} | ||
<p>{{ translate("Defaults to product price set in NetSuite") }}</p> | ||
</ion-label> | ||
</ion-radio> | ||
</ion-item> | ||
<ion-item> | ||
<ion-radio value="Custom" label-placement="end" justify="start"> | ||
<ion-label> | ||
{{ translate("Custom") }} | ||
<p>{{ translate("Use the price a product was sold at in the order.") }}</p> | ||
</ion-label> | ||
</ion-radio> | ||
</ion-item> | ||
</ion-radio-group> | ||
</ion-list> | ||
|
||
<ion-fab vertical="bottom" horizontal="end" slot="fixed"> | ||
<ion-fab-button :disabled="isPriceLevelChanged()" @click="savePrice"> | ||
<ion-icon :icon="saveOutline" /> | ||
</ion-fab-button> | ||
</ion-fab> | ||
</ion-content> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import { IonButton, IonButtons, IonContent, IonFab, IonFabButton, IonHeader, IonIcon, IonInput, IonItem, IonLabel, IonList, IonListHeader, IonRadio, IonRadioGroup, IonTitle, IonToolbar, modalController } from "@ionic/vue"; | ||
import { closeOutline, informationCircleOutline, openOutline, saveOutline } from 'ionicons/icons'; | ||
import { translate } from "@/i18n" | ||
import { useStore } from "vuex" | ||
import { ref, onMounted } from "vue" | ||
import { useNetSuiteComposables } from "@/composables/useNetSuiteComposables"; | ||
const store = useStore(); | ||
const priceLevelTypeId = JSON.parse(process.env.VUE_APP_NETSUITE_INTEGRATION_TYPE_MAPPING)?.PRICE_LEVEL_TYPE_ID | ||
const { updateNetSuiteId } = useNetSuiteComposables(priceLevelTypeId); | ||
const integrationMapping = ref("") as any; | ||
const selectedPriceLevel = ref("") | ||
onMounted(async () => { | ||
await store.dispatch("netSuite/fetchIntegrationTypeMappings", { | ||
integrationTypeId: priceLevelTypeId, | ||
mappingKey: "PRICE_LEVEL" | ||
}) | ||
const integrationMappings = store.getters["netSuite/getIntegrationTypeMappings"](priceLevelTypeId); | ||
selectedPriceLevel.value = (integrationMapping.value = integrationMappings[0]).mappingValue || ""; | ||
}) | ||
function isPriceLevelChanged() { | ||
return (!selectedPriceLevel.value.trim() || selectedPriceLevel.value.trim() === integrationMapping.value.mappingValue) | ||
} | ||
// saves the selectedPriceLevel price level to Netsuite for integration type id: 'NETSUITE_PRICE_LEVEL' & mappingKey: 'PRICE_LEVEL'. | ||
async function savePrice() { | ||
const payload = { | ||
integrationTypeId: priceLevelTypeId, | ||
mappingKey: "PRICE_LEVEL", | ||
mappingValue: selectedPriceLevel.value | ||
}; | ||
await updateNetSuiteId(payload, integrationMapping.value.integrationMappingId); | ||
closeModal(); | ||
} | ||
function closeModal() { | ||
modalController.dismiss({ dismissed: true }); | ||
} | ||
function openPriceLevelDoc() { | ||
window.open('https://docs.hotwax.co/documents/v/learn-netsuite/synchronization-flows/integration-mappings/price-levels', '_blank', 'noopener, noreferrer'); | ||
} | ||
</script> |
Oops, something went wrong.