diff --git a/.env.example b/.env.example index e25e5836..62326eb2 100644 --- a/.env.example +++ b/.env.example @@ -18,6 +18,12 @@ VUE_APP_ALIAS= VUE_APP_DEFAULT_LOG_LEVEL="error" VUE_APP_LOGIN_URL="http://launchpad.hotwax.io/login" VUE_APP_BROKER_JOB_ENUMS = {"REJ_ORDR":"JOB_BKR_REJ_ORD"} +VUE_APP_FULFILLMENT_MAARG_JOB_ENUMS={"SND_FF_ACK_FEED":"SND_SHPFY_FUL_ACK_FD","GNRT_FF_ORD_ITM_FEED":"GEN_FLFLD_ORD_ITM_FD","POL_OMS_FLFLMNT_FEED":"POL_OMS_FLFLMNT_FD","GNRT_TO_FLFLD_ITM_FEED":"GEN_TO_FLFD_ITM_FD"} +VUE_APP_INVENTORY_MAARG_JOB_ENUMS={"GNRT_SHIP_RCPT_FEED":"GEN_SHPMNT_RCPT_FD","GEN_INV_VAR_FEED":"GEN_INV_VAR_FD","GEN_INVCYC_COUNT_VAR_FEED":"GEN_INV_CYCLE_VAR_FD"} +VUE_APP_ORDERS_MAARG_JOB_ENUMS={"GEN_BRKD_ORDITM_FEED":"GEN_BRKR_ORD_ITM_FD","GEN_APPEASE_FIN_FEED":"GEN_APSMNT_FNCL_FD", "GEN_RTRN_FIN_FEED": "GEN_RTNS_FNCL_FD"} +VUE_APP_MISC_MAARG_JOB_ENUMS={"BLK_SYS_MESS_SHPFY": "SND_BLK_SYS_M_SHPFY","BLK_RSLT_SHPFY":"POL_BLK_RSLT_SHPFY","ALL_RCVD_SYS_MSG":"CNSM_RCVD_SYS_MSG","ALL_PRDCD_SYS_MSG":"SND_PRDCD_SYS_MSG"} +VUE_APP_PREORD_MAARG_JOB_ENUMS={"PO_RCPT_FEED": "GEN_PO_RCPT_FD"} +VUE_APP_CRON_EXPRESSIONS={"Every 5 minutes":"0 */5 * ? * *","Every 15 minutes":"0 */15 * ? * *","Every 30 minutes":"0 */30 * ? * *","Hourly":"0 0 * ? * *","Every six hours":"0 0 */6 ? * *","Every day at midnight":"0 0 0 * * ?"} VUE_APP_GITBOOK_API_KEY="" VUE_APP_SPACE_ID="" VUE_APP_GITBOOK_BASE_URL="https://api.gitbook.com/v1" diff --git a/package-lock.json b/package-lock.json index af464c11..96e8f838 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,8 @@ "@types/papaparse": "^5.3.1", "boon-js": "^2.0.3", "core-js": "^3.6.5", + "cron-parser": "^4.9.0", + "cronstrue": "^2.50.0", "file-saver": "^2.0.5", "luxon": "^3.2.0", "mitt": "^2.1.0", @@ -6196,6 +6198,25 @@ "node": ">=8" } }, + "node_modules/cron-parser": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", + "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", + "dependencies": { + "luxon": "^3.2.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/cronstrue": { + "version": "2.51.0", + "resolved": "https://registry.npmjs.org/cronstrue/-/cronstrue-2.51.0.tgz", + "integrity": "sha512-7EG9VaZZ5SRbZ7m25dmP6xaS0qe9ay6wywMskFOU/lMDKa+3gZr2oeT5OUfXwRP/Bcj8wxdYJ65AHU70CI3tsw==", + "bin": { + "cronstrue": "bin/cli.js" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "dev": true, diff --git a/package.json b/package.json index 6df3d9f4..fffbdf29 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,8 @@ "@ionic/vue-router": "^7.6.0", "@types/file-saver": "^2.0.4", "@types/papaparse": "^5.3.1", + "cronstrue": "^2.50.0", + "cron-parser": "^4.9.0", "boon-js": "^2.0.3", "core-js": "^3.6.5", "file-saver": "^2.0.5", diff --git a/src/components/JobHistoryModal.vue b/src/components/JobHistoryModal.vue index 75a796be..e3b7f873 100644 --- a/src/components/JobHistoryModal.vue +++ b/src/components/JobHistoryModal.vue @@ -6,7 +6,8 @@ - {{ currentJob?.enumName }} + {{ currentJob?.enumDescription ? currentJob.enumDescription : currentJob.jobName }} + {{ currentJob?.enumName ? currentJob.enumName : currentJob?.description }} @@ -16,7 +17,18 @@
- + + + + {{ job.runTime ? getTime(job.runTime) : "-" }} @@ -49,8 +61,10 @@ import { closeOutline } from 'ionicons/icons'; import { mapGetters, useStore } from 'vuex'; import { DateTime } from 'luxon'; import { JobService } from '@/services/JobService' -import { hasError } from '@/utils'; +import { hasError, timeTillRun } from '@/utils'; import { translate } from '@hotwax/dxp-components'; +import logger from '@/logger'; +import { MaargJobService } from '@/services/MaargJobService'; export default defineComponent({ name: 'JobHistoryModal', @@ -69,10 +83,10 @@ export default defineComponent({ }, data() { return { - jobHistory: [] + jobHistory: [] as any } }, - props: ['currentJob'], + props: ['currentJob', 'isMaargJob'], computed: { ...mapGetters({ getCurrentEComStore:'user/getCurrentEComStore', @@ -119,10 +133,27 @@ export default defineComponent({ } catch(err) { this.$log.error(err); } + }, + async fetchMaargJobHistory() { + try { + const resp = await MaargJobService.fetchMaargJobHistory({ + jobName: this.currentJob.jobName, + pageSize: 200, + orderByField: "startTime DESC" + }); + + if(!hasError(resp)) { + this.jobHistory = resp.data + } else { + throw resp; + } + } catch(error: any) { + logger.error(error); + } } }, mounted() { - this.fetchJobHistory() + this.isMaargJob ? this.fetchMaargJobHistory() : this.fetchJobHistory() }, setup() { const store = useStore(); @@ -130,6 +161,7 @@ export default defineComponent({ return { closeOutline, store, + timeTillRun, translate }; }, diff --git a/src/components/MaargJobConfiguration.vue b/src/components/MaargJobConfiguration.vue new file mode 100644 index 00000000..cd8d24bb --- /dev/null +++ b/src/components/MaargJobConfiguration.vue @@ -0,0 +1,446 @@ + + + + + \ No newline at end of file diff --git a/src/components/MaargJobParameterModal.vue b/src/components/MaargJobParameterModal.vue new file mode 100644 index 00000000..ffe71b0c --- /dev/null +++ b/src/components/MaargJobParameterModal.vue @@ -0,0 +1,134 @@ + + + \ No newline at end of file diff --git a/src/components/ScheduleModal.vue b/src/components/ScheduleModal.vue new file mode 100644 index 00000000..ddbcb46d --- /dev/null +++ b/src/components/ScheduleModal.vue @@ -0,0 +1,158 @@ + + \ No newline at end of file diff --git a/src/locales/en.json b/src/locales/en.json index c033f521..cb1925bf 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -17,6 +17,7 @@ "App": "App", "Approve orders": "Approve orders", "Archived": "Archived", + "Are you sure you want to enable this job?": "Are you sure you want to enable this job?", "Are you sure you want to save these changes?": "Are you sure you want to save these changes?", "Are you sure you want to schedule these jobs?": "Are you sure you want to schedule these jobs?", "Authenticating": "Authenticating", @@ -59,6 +60,8 @@ "Close": "Close", "Completed orders": "Completed orders", "Configuration missing": "Configuration missing", + "Confirm": "Confirm", + "Consume All Received System Messages": "Consume All Received System Messages", "Copied job details to clipboard": "Copied job details to clipboard", "Copied to clipboard": "Copied to clipboard", "Copy details": "Copy details", @@ -85,15 +88,20 @@ "eCommerce": "eCommerce", "eCommerce stores are directly connected to one Shop Config. If your OMS is connected to multiple eCommerce stores selling the same catalog operating as one Company, you may have multiple Shop Configs for the selected Product Store.": "eCommerce stores are directly connected to one Shop Config. If your OMS is connected to multiple eCommerce stores selling the same catalog operating as one Company, you may have multiple Shop Configs for the selected Product Store.", "Email customers": "Email customers", + "Enable": "Enable", + "Enable job": "Enable job", "Every 15 minutes": "Every 15 minutes", "Every 30 minutes": "Every 30 minutes", "Every 5 minutes": "Every 5 minutes", "Every 6 hours": "Every 6 hours", "Every day": "Every day", + "Expression": "Expression", "Failed": "Failed", "Failed records": "Failed records", "Failed job reason": "Failed job reason", + "Failed to cancel job": "Failed to cancel job", "Failed to schedule service(s)": "Failed to schedule {count} service(s)", + "Failed to update service": "Failed to update service", "Fetching jobs": "Fetching jobs", "Fetching TimeZones": "Fetching TimeZones", "File upload status": "File upload status", @@ -107,6 +115,15 @@ "Fulfillment": "Fulfillment", "Fulfillment status": "Fulfillment status", "Generating answer...": "Generating answer...", + "Generate appeasements financial feed": "Generate appeasements financial feed", + "Generate Brokered order items feed": "Generate Brokered order items feed", + "Generate inventory cycle count variance feed": "Generate inventory cycle count variance feed", + "Generate inventory variance feed": "Generate inventory variance feed", + "Generate order items feed": "Generate order items feed", + "Generate PO shipment receipt feed": "Generate PO shipment receipt feed", + "Generate returns financial feed": "Generate returns financial feed", + "Generate Shipments Receipt Feed": "Generate Shipments Receipt Feed", + "Generate TO order items feed": "Generate TO order items feed", "Get Paid Transactions": "Get Paid Transactions", "Go to OMS": "Go to OMS", "Go to Launchpad": "Go to Launchpad", @@ -133,6 +150,7 @@ "Import logs": "Import logs", "Job": "Job", "Job details": "Job details", + "Job has been cancelled succesfully.": "Job has been cancelled succesfully.", "Job runtime has passed. The job data has refreshed. Please try again.": "Job runtime has passed. The job data has refreshed. Please try again.", "Job runtime has passed. Please refresh to get the latest job data in order to perform any action.": "Job runtime has passed. Please refresh to get the latest job data in order to perform any action.", "Job Manager": "Job Manager", @@ -192,6 +210,10 @@ "Pinned": "Pinned", "Pinned jobs": "Pinned jobs", "Pipeline": "Pipeline", + "Provide a valid cron expression": "Provide a valid cron expression", + "Please select a scheduling for job": "Please select a scheduling for job", + "Poll current bulk operation query result": "Poll current bulk operation query result", + "Poll OMS fulfilled items feed": "Poll OMS fulfilled items feed", "Pre-order": "Pre-order", "Pre-Order": "Pre-Order", "Pre-order parking": "Pre-order parking", @@ -242,6 +264,7 @@ "Schedule": "Schedule", "Schedule in bulk": "Schedule in bulk", "Schedule inventory hard sync": "Schedule inventory hard sync", + "Schedule Options": "Schedule Options", "Schedule product sync": "Schedule product sync", "Scheduled Job": "Scheduled Job", "Scheduler": "Scheduler", @@ -257,6 +280,9 @@ "Selected TimeZone": "Selected TimeZone", "Select a different time zone": "Select a different time zone", "Select time zone": "Select time zone", + "Send All Produced System Messages": "Send All Produced System Messages", + "Send next bulk query system message in queue": "Send next bulk query system message in queue", + "Send shopify fulfillment ack feed": "Send shopify fulfillment ack feed", "Sends notifications for open orders and ready-to-pickup orders.":"Sends notifications for open orders and ready-to-pickup orders.", "Service has been scheduled": "Service has been scheduled", "Service updated successfully": "Service updated successfully", @@ -273,6 +299,7 @@ "Skip job": "Skip job", "Skip once": "Skip once", "Skipping will run this job at the next occurrence based on the temporal expression.": "Skipping will run this job at the next occurrence based on the temporal expression.", + "Some of the app functionality will not work due to missing configuration.": "Some of the app functionality will not work due to missing configuration.", "Sources": "Sources", "Some jobs have slow frequency type, hence, feasible frequency will be set automatically": "Some jobs have slow frequency type, hence, feasible frequency will be set automatically", "Something went wrong": "Something went wrong", diff --git a/src/services/MaargJobService.ts b/src/services/MaargJobService.ts new file mode 100644 index 00000000..219c4885 --- /dev/null +++ b/src/services/MaargJobService.ts @@ -0,0 +1,109 @@ +import store from '@/store'; +import { client } from '@/adapter'; + + +const fetchMaargJobs = async (payload: any): Promise => { + const omsRedirectionInfo = store.getters['user/getOmsRedirectionInfo']; + const baseURL = store.getters['user/getMaargBaseUrl']; + + return client({ + url: "serviceJobs", + method: "GET", + baseURL, + headers: { + "api_key": omsRedirectionInfo.token, + "Content-Type": "application/json" + }, + params: payload + }); +} + +const fetchMaargJobInfo = async (jobName: any): Promise => { + const omsRedirectionInfo = store.getters['user/getOmsRedirectionInfo']; + const baseURL = store.getters['user/getMaargBaseUrl']; + + return client({ + url: `serviceJobs/${jobName}`, + method: "GET", + baseURL, + headers: { + "api_key": omsRedirectionInfo.token, + "Content-Type": "application/json" + } + }); +} + + +const runNow = async (jobName: any): Promise => { + const omsRedirectionInfo = store.getters['user/getOmsRedirectionInfo']; + const baseURL = store.getters['user/getMaargBaseUrl']; + + return client({ + url: `serviceJobs/${jobName}/runNow`, + method: "POST", + baseURL, + headers: { + "api_key": omsRedirectionInfo.token, + "Content-Type": "application/json" + } + }); +} + +const updateMaargJob = async (payload: any): Promise => { + const omsRedirectionInfo = store.getters['user/getOmsRedirectionInfo']; + const baseURL = store.getters['user/getMaargBaseUrl']; + + return client({ + url: `serviceJobs/${payload.jobName}`, + method: "POST", + baseURL, + data: payload, + headers: { + "api_key": omsRedirectionInfo.token, + "Content-Type": "application/json" + } + }); +} + +const fetchMaargJobHistory = async (payload: any): Promise => { + const omsRedirectionInfo = store.getters['user/getOmsRedirectionInfo']; + const baseURL = store.getters['user/getMaargBaseUrl']; + + return client({ + url: `serviceJobs/${payload.jobName}/runs`, + method: "GET", + baseURL, + params: payload, + headers: { + "api_key": omsRedirectionInfo.token, + "Content-Type": "application/json" + } + }); +} + +const fetchMaargJobEnumerations = async (payload: any): Promise => { + const omsRedirectionInfo = store.getters['user/getOmsRedirectionInfo']; + const baseURL = store.getters['user/getMaargBaseUrl']; + + const updatedBaseUrl = baseURL.replace("admin", "available-to-promise") + + return client({ + url: "enums", + method: "GET", + baseURL: updatedBaseUrl, + params: payload, + headers: { + "api_key": omsRedirectionInfo.token, + "Content-Type": "application/json" + } + }); +} + +export const MaargJobService = { + fetchMaargJobEnumerations, + fetchMaargJobs, + fetchMaargJobHistory, + fetchMaargJobInfo, + runNow, + updateMaargJob +} \ No newline at end of file diff --git a/src/services/UserService.ts b/src/services/UserService.ts index 3b02477c..e4567d5f 100644 --- a/src/services/UserService.ts +++ b/src/services/UserService.ts @@ -14,6 +14,34 @@ const login = async (username: string, password: string): Promise => { }); } +const moquiLogin = async (omsRedirectionUrl: string, token: string): Promise => { + const baseURL = omsRedirectionUrl.startsWith('http') ? omsRedirectionUrl.includes('/rest/s1/admin') ? omsRedirectionUrl : `${omsRedirectionUrl}/rest/s1/admin/` : `https://${omsRedirectionUrl}.hotwax.io/rest/s1/admin/`; + let api_key = "" + + try { + const resp = await client({ + url: "login", + method: "post", + baseURL, + params: { + token + }, + headers: { + "Content-Type": "application/json" + } + }) as any; + + if(!hasError(resp) && (resp.data.api_key || resp.data.token)) { + api_key = resp.data.api_key || resp.data.token + } else { + throw "Sorry, login failed. Please try again"; + } + } catch(err) { + logger.error(err) + return Promise.resolve(""); + } + return Promise.resolve(api_key) +} const getShopifyConfig = async (productStoreId: any, token?: any): Promise => { try { @@ -362,5 +390,6 @@ export const UserService = { associatePinnedJobPrefToUser, updatePinnedJobPref, setUserPreference, - getUserPermissions + getUserPermissions, + moquiLogin } \ No newline at end of file diff --git a/src/store/RootState.ts b/src/store/RootState.ts index 99f6acf0..041ee3c4 100644 --- a/src/store/RootState.ts +++ b/src/store/RootState.ts @@ -2,4 +2,5 @@ export default interface RootState { user: any; product: any; util: any; + maargJob: any; } \ No newline at end of file diff --git a/src/store/index.ts b/src/store/index.ts index ad7f24d9..08b10940 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -10,6 +10,7 @@ import jobModule from "./modules/job" import utilModule from "./modules/util" import webhookModule from "./modules/webhook" import { setPermissions } from '@/authorization' +import maargJobModule from "./modules/maargJob" // TODO check how to register it from the components only @@ -22,7 +23,7 @@ const state: any = { } const persistState = createPersistedState({ - paths: ['user', 'util'], + paths: ['user', 'util', 'maargJob.maargJobEnums'], fetchBeforeUse: true }) @@ -38,6 +39,7 @@ const store = createStore({ 'product': productModule, 'job': jobModule, 'util': utilModule, + 'maargJob': maargJobModule, 'webhook': webhookModule }, }) diff --git a/src/store/modules/maargJob/MaargJobState.ts b/src/store/modules/maargJob/MaargJobState.ts new file mode 100644 index 00000000..e36846e2 --- /dev/null +++ b/src/store/modules/maargJob/MaargJobState.ts @@ -0,0 +1,5 @@ +export default interface MaargJobState { + maargJobs: any; + currentMaargJob: any; + maargJobEnums: any; +} \ No newline at end of file diff --git a/src/store/modules/maargJob/actions.ts b/src/store/modules/maargJob/actions.ts new file mode 100644 index 00000000..8ebd7fb8 --- /dev/null +++ b/src/store/modules/maargJob/actions.ts @@ -0,0 +1,122 @@ +import { ActionTree } from 'vuex' +import RootState from '@/store/RootState' +import JobState from './MaargJobState' +import * as types from './mutation-types' +import { hasError } from '@/utils' +import store from '@/store' +import logger from "@/logger"; +import { MaargJobService } from '@/services/MaargJobService' + +const actions: ActionTree = { + async fetchMaargJobs({ commit, dispatch }, jobTypeEnumIds){ + const productStoreId = store.getters["user/getCurrentEComStore"]?.productStoreId + const maargJobEnums = store.getters["maargJob/getMaargJobEnums"] + + let resp = {} as any; + const maargJobs = {} as any; + + try { + resp = await MaargJobService.fetchMaargJobs({ jobTypeEnumId: jobTypeEnumIds, jobTypeEnumId_op: "in" }) + if(!hasError(resp) && resp.data?.length) { + const jobs = resp.data; + const jobEnumIdsToFetch = jobs.map((job: any) => job.jobTypeEnumId); + + const responses = await Promise.allSettled(jobs.map((job: any) => MaargJobService.fetchMaargJobInfo(job.jobName))) + await dispatch("fetchMaargJobEnums", jobEnumIdsToFetch) + + responses.map((response: any) => { + if(response.status === "fulfilled") { + const job = response.value.data.jobDetail + const paramValues = {} as any; + + job.serviceJobParameters.map((parameter: any) => { + paramValues[parameter.parameterName] = parameter.parameterValue + }) + job["parameterValues"] = paramValues + job["enumDescription"] = maargJobEnums[job.jobTypeEnumId]?.description + + if(Object.hasOwn(paramValues, "productStoreIds")) { + if(paramValues["productStoreIds"] === productStoreId) { + maargJobs[job.jobTypeEnumId] = job + } + } else { + // For handling case where we have childs jobs for the productstore independent job + // We'll give preference to job with parentJobName and then the job without parentJobName + if(!maargJobs[job.jobTypeEnumId] || (maargJobEnums[job.jobTypeEnumId] && !maargJobEnums[job.jobTypeEnumId]?.parentJobName)) { + maargJobs[job.jobTypeEnumId] = job + } + } + } + }) + } else { + throw resp; + } + } catch(error: any) { + logger.error(error); + } + + commit(types.MAARGJOB_UPDATED, maargJobs); + }, + + async updateMaargJob({ commit, state }, jobEnumId) { + const jobs = JSON.parse(JSON.stringify(state.maargJobs)); + const currentJob = jobs[jobEnumId] + + try { + const resp = await MaargJobService.fetchMaargJobInfo(currentJob.jobName); + if(!hasError(resp)) { + const currentJob = resp.data?.jobDetail + + const paramValue = {} as any; + currentJob.serviceJobParameters.map((parameter: any) => { + paramValue[parameter.parameterName] = parameter.parameterValue + }) + currentJob["parameterValues"] = paramValue + + jobs[jobEnumId] = currentJob + commit(types.MAARGJOB_UPDATED, jobs); + commit(types.MAARGJOB_CURRENT_UPDATED, currentJob); + } else { + throw resp; + } + } catch(error: any) { + logger.error(error); + } + }, + + async fetchMaargJobEnums({ commit }, enumIds) { + const jobEnums = JSON.parse(JSON.stringify(store.getters["maargJob/getMaargJobEnums"])) + const enumIdsToFetch = enumIds.filter((enumId: any) => !jobEnums[enumId]) + + if(!enumIdsToFetch.length) return; + + try { + const resp = await MaargJobService.fetchMaargJobEnumerations({ enumId: enumIdsToFetch, enumId_op: "in" }); + + if(!hasError(resp)) { + resp.data.map((enumeration: any) => { + jobEnums[enumeration.enumId] = enumeration + }) + } else { + throw resp; + } + } catch(error) { + logger.error(error); + } + commit(types.MAARGJOB_ENUMS_UPDATED, jobEnums); + }, + + async updateCurrentMaargJob({ commit }, payload) { + if(payload?.job) { + commit(types.MAARGJOB_CURRENT_UPDATED, payload.job); + return payload?.job; + } + + // Todo: refetch job for the mobile view of job details. + }, + + async clearMaargJobState({ commit }) { + commit(types.MAARGJOB_ENUMS_UPDATED, {}); + } +} +export default actions; diff --git a/src/store/modules/maargJob/getters.ts b/src/store/modules/maargJob/getters.ts new file mode 100644 index 00000000..8110f52b --- /dev/null +++ b/src/store/modules/maargJob/getters.ts @@ -0,0 +1,17 @@ +import { GetterTree } from 'vuex' +import MaargJobState from './MaargJobState' +import RootState from '../../RootState' + +const getters: GetterTree = { + getMaargJob: (state) => (jobTypeEnumId: string): any => { + return state.maargJobs[jobTypeEnumId] ? state.maargJobs[jobTypeEnumId] : {} + }, + getCurrentMaargJob: (state) => { + return state.currentMaargJob + }, + getMaargJobEnums (state) { + return state.maargJobEnums + } +} + +export default getters; \ No newline at end of file diff --git a/src/store/modules/maargJob/index.ts b/src/store/modules/maargJob/index.ts new file mode 100644 index 00000000..7e678719 --- /dev/null +++ b/src/store/modules/maargJob/index.ts @@ -0,0 +1,20 @@ +import actions from './actions' +import getters from './getters' +import mutations from './mutations' +import { Module } from 'vuex' +import MaargJobState from './MaargJobState' +import RootState from '../../RootState' + +const maargJobModule: Module = { + namespaced: true, + state: { + maargJobs: {}, + currentMaargJob: {}, + maargJobEnums: {} + }, + getters, + actions, + mutations, +} + +export default maargJobModule; \ No newline at end of file diff --git a/src/store/modules/maargJob/mutation-types.ts b/src/store/modules/maargJob/mutation-types.ts new file mode 100644 index 00000000..17a1fb0b --- /dev/null +++ b/src/store/modules/maargJob/mutation-types.ts @@ -0,0 +1,6 @@ +export const SN_MAARGJOB = 'maargJob' +export const MAARGJOB_UPDATED_BULK = SN_MAARGJOB + '/UPDATED_BULK' +export const MAARGJOB_UPDATED = SN_MAARGJOB + '/MAARG_JOBS_UPDATED' +export const MAARGJOB_CURRENT_UPDATED = SN_MAARGJOB + '/CURRENT_JOB_UPDATED' +export const MAARGJOB_ENUMS_UPDATED = SN_MAARGJOB + '/JOB_ENUMS_UPDATED' + diff --git a/src/store/modules/maargJob/mutations.ts b/src/store/modules/maargJob/mutations.ts new file mode 100644 index 00000000..41d5dede --- /dev/null +++ b/src/store/modules/maargJob/mutations.ts @@ -0,0 +1,16 @@ +import { MutationTree } from 'vuex' +import MaargJobState from './MaargJobState' +import * as types from './mutation-types' + +const mutations: MutationTree = { + [types.MAARGJOB_UPDATED] (state, payload) { + state.maargJobs = payload + }, + [types.MAARGJOB_CURRENT_UPDATED] (state, payload) { + state.currentMaargJob = payload + }, + [types.MAARGJOB_ENUMS_UPDATED] (state, payload) { + state.maargJobEnums = payload + }, +} +export default mutations; \ No newline at end of file diff --git a/src/store/modules/user/UserState.ts b/src/store/modules/user/UserState.ts index 84f86c9c..e933b589 100644 --- a/src/store/modules/user/UserState.ts +++ b/src/store/modules/user/UserState.ts @@ -8,4 +8,8 @@ export default interface UserState { currentShopifyConfig: any; productStoreCategories: any; pwaState: any; + omsRedirectionInfo: { + url: string; + token: string; + } } \ No newline at end of file diff --git a/src/store/modules/user/actions.ts b/src/store/modules/user/actions.ts index 3fa8beb7..0e92bf26 100644 --- a/src/store/modules/user/actions.ts +++ b/src/store/modules/user/actions.ts @@ -11,6 +11,7 @@ import { logout, updateInstanceUrl, updateToken, resetConfig } from '@/adapter' import logger from "@/logger"; import { useAuthStore } from '@hotwax/dxp-components' import emitter from '@/event-bus' +import store from '@/store' const actions: ActionTree = { @@ -22,7 +23,7 @@ const actions: ActionTree = { */ async login({ commit, dispatch }, payload) { try { - const { token, oms } = payload + const { token, oms, omsRedirectionUrl } = payload dispatch("setUserInstanceUrl", oms); // Getting the permissions list from server @@ -84,6 +85,19 @@ const actions: ActionTree = { Settings.defaultZone = userProfile.userTimeZone; } + if(omsRedirectionUrl) { + const api_key = await UserService.moquiLogin(omsRedirectionUrl, token) + if(api_key) { + dispatch("setOmsRedirectionInfo", { url: omsRedirectionUrl, token: api_key }) + } else { + showToast(translate("Some of the app functionality will not work due to missing configuration.")) + logger.error("Some of the app functionality will not work due to missing configuration."); + } + } else { + showToast(translate("Some of the app functionality will not work due to missing configuration.")) + logger.error("Some of the app functionality will not work due to missing configuration.") + } + // TODO user single mutation commit(types.USER_CURRENT_ECOM_STORE_UPDATED, preferredStore); commit(types.USER_INFO_UPDATED, userProfile); @@ -107,7 +121,7 @@ const actions: ActionTree = { /** * Logout user */ - async logout ({ commit, dispatch }, payload) { + async logout ({ commit }, payload) { // store the url on which we need to redirect the user after logout api completes in case of SSO enabled let redirectionUrl = '' @@ -135,7 +149,8 @@ const actions: ActionTree = { const authStore = useAuthStore() // TODO add any other tasks if need - dispatch('job/clearJobState', null, { root: true }); + store.dispatch('job/clearJobState', null, { root: true }); + store.dispatch('maargJob/clearMaargJobState'); commit(types.USER_END_SESSION) resetConfig(); resetPermissions(); @@ -180,6 +195,10 @@ const actions: ActionTree = { updateInstanceUrl(payload) }, + setOmsRedirectionInfo({ commit }, payload) { + commit(types.USER_OMS_REDIRECTION_INFO_UPDATED, payload) + }, + updatePwaState({commit}, payload) { commit(types.USER_PWA_STATE_UPDATED, payload); }, diff --git a/src/store/modules/user/getters.ts b/src/store/modules/user/getters.ts index b0f6419f..e3aa9a49 100644 --- a/src/store/modules/user/getters.ts +++ b/src/store/modules/user/getters.ts @@ -44,6 +44,13 @@ const getters: GetterTree = { }, getPinnedJobs(state) { return state.current ? (state.current as any)['pinnedJobs']?.jobs : [] + }, + getMaargBaseUrl (state) { + const url = state.omsRedirectionInfo.url + return url.startsWith('http') ? url.includes('/rest/s1/admin') ? url : `${url}/rest/s1/admin/` : `https://${url}.hotwax.io/rest/s1/admin/`; + }, + getOmsRedirectionInfo(state) { + return state.omsRedirectionInfo } } export default getters; \ No newline at end of file diff --git a/src/store/modules/user/index.ts b/src/store/modules/user/index.ts index 90bc9c82..c1e26305 100644 --- a/src/store/modules/user/index.ts +++ b/src/store/modules/user/index.ts @@ -23,6 +23,10 @@ const userModule: Module = { updateExists: false, registration: null, }, + omsRedirectionInfo: { + url: "", + token: "" + } }, getters, actions, diff --git a/src/store/modules/user/mutation-types.ts b/src/store/modules/user/mutation-types.ts index f36325e3..cbbf1ea3 100644 --- a/src/store/modules/user/mutation-types.ts +++ b/src/store/modules/user/mutation-types.ts @@ -8,4 +8,5 @@ export const USER_CURRENT_SHOPIFY_CONFIG_UPDATED = SN_USER + '/SHOPIFY_CONFIG_UP export const USER_CURRENT_ECOM_STORE_UPDATED = SN_USER + '/CURRENT_ECOM_STORE_UPDATED' export const USER_PERMISSIONS_UPDATED = SN_USER + '/PERMISSIONS_UPDATED' export const USER_PWA_STATE_UPDATED = SN_USER + '/PWA_STATE_UPDATED' -export const USER_PRDCT_STR_CATGRS_UPDATED = SN_USER + '/PRDCT_STR_CATGRS_UPDATED' \ No newline at end of file +export const USER_PRDCT_STR_CATGRS_UPDATED = SN_USER + '/PRDCT_STR_CATGRS_UPDATED' +export const USER_OMS_REDIRECTION_INFO_UPDATED = SN_USER + '/OMS_REDIRECTION_INFO_UPDATED' \ No newline at end of file diff --git a/src/store/modules/user/mutations.ts b/src/store/modules/user/mutations.ts index b3003c9e..845b95b0 100644 --- a/src/store/modules/user/mutations.ts +++ b/src/store/modules/user/mutations.ts @@ -17,6 +17,10 @@ const mutations: MutationTree = { state.shopifyConfigs = [] state.permissions = [] state.productStoreCategories = {} + state.omsRedirectionInfo = { + url: "", + token: "" + } }, [types.USER_INFO_UPDATED] (state, payload) { state.current = { ...state.current, ...payload} @@ -43,5 +47,8 @@ const mutations: MutationTree = { [types.USER_PERMISSIONS_UPDATED] (state, payload) { state.permissions = payload }, + [types.USER_OMS_REDIRECTION_INFO_UPDATED](state, payload) { + state.omsRedirectionInfo = payload; + } } export default mutations; diff --git a/src/utils/index.ts b/src/utils/index.ts index 313e99ca..7c23e798 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -5,6 +5,7 @@ import { DateTime } from "luxon"; import logger from "@/logger"; import { translate } from "@hotwax/dxp-components"; import { Plugins } from '@capacitor/core'; +import cronstrue from "cronstrue" // TODO Use separate files for specific utilities @@ -354,6 +355,57 @@ const saveDataFile = async (response: any, fileName: string) => { saveAs(blob, fileName); } +function getDateAndTime(time: any) { + return time ? DateTime.fromMillis(time).toLocaleString({ ...DateTime.DATETIME_MED, hourCycle: "h12" }) : "-"; +} + +function timeTillRun(endTime: any) { + const timeDiff = DateTime.fromMillis(endTime).diff(DateTime.local()); + return DateTime.local().plus(timeDiff).toRelative(); +} + +function getCronString(cronExpression: any) { + try { + return cronstrue.toString(cronExpression) + } catch(e) { + logger.error(e) + return "" + } +} + +const generateMaargJobCustomOptions = (job: any) => { + let inputParameters = job?.serviceInParameters ? JSON.parse(JSON.stringify(job?.serviceInParameters)) : [] + const optionalParameters: Array = []; + const requiredParameters: Array = []; + + // removing some fields that we don't want user to edit, and for which the values will be added programatically + const excludeParameters = ['productStoreIds'] + inputParameters = inputParameters.filter((parameter: any) =>!excludeParameters.includes(parameter.name)) + + inputParameters.map((parameter: any) => { + if(parameter.required === "true") { + requiredParameters.push({ + name: parameter.name, + value: job?.parameterValues && job?.parameterValues[parameter.name] && job?.parameterValues[parameter.name] !== 'null' ? convertToString({ value: job?.parameterValues[parameter.name], type: parameter.type }) : '', // added check for null as we don't want to pass null as a value in the params + type: parameter.type, + default: parameter.default + }) + } else { + optionalParameters.push({ + name: parameter.name, + value: job?.parameterValues && job?.parameterValues[parameter.name] && job?.parameterValues[parameter.name] !== 'null' ? convertToString({ value: job?.parameterValues[parameter.name], type: parameter.type }) : '', // added check for null as we don't want to pass null as a value in the params + type: parameter.type, + default: parameter.default + }) + } + }) + + return { + optionalParameters, + requiredParameters + } +} + export { copyToClipboard, isCustomRunTime, @@ -362,6 +414,9 @@ export { generateAllowedRunTimes, generateJobCustomParameters, generateJobCustomOptions, + generateMaargJobCustomOptions, + getCronString, + getDateAndTime, handleDateTimeInput, hasJobDataError, showToast, @@ -371,5 +426,6 @@ export { JsonToCsvOption, isFutureDate, prepareRuntime, - saveDataFile + saveDataFile, + timeTillRun } diff --git a/src/views/Fulfillment.vue b/src/views/Fulfillment.vue index 2e8a3c11..36891f88 100644 --- a/src/views/Fulfillment.vue +++ b/src/views/Fulfillment.vue @@ -91,11 +91,35 @@

{{ translate("Unfulfilled orders that pass their auto cancelation date will be canceled automatically in HotWax Commerce. They will also be canceled in Shopify if upload for canceled orders is enabled.") }}

+ + + + {{ translate("Feed") }} + + + {{ translate("Generate order items feed") }} + {{ isMaargJobFound('GNRT_FF_ORD_ITM_FEED') ? getMaargJobStatus("GNRT_FF_ORD_ITM_FEED") : translate("Not found") }} + + + {{ translate("Send shopify fulfillment ack feed") }} + {{ isMaargJobFound('SND_FF_ACK_FEED') ? getMaargJobStatus("SND_FF_ACK_FEED") : translate("Not found") }} + + + {{ translate("Poll OMS fulfilled items feed") }} + {{ isMaargJobFound('POL_OMS_FLFLMNT_FEED') ? getMaargJobStatus("POL_OMS_FLFLMNT_FEED") : translate("Not found") }} + + + {{ translate("Generate TO order items feed") }} + {{ isMaargJobFound('GNRT_TO_FLFLD_ITM_FEED') ? getMaargJobStatus("GNRT_TO_FLFLD_ITM_FEED") : translate("Not found") }} + + + -