diff --git a/src/components/BatchModal.vue b/src/components/BatchModal.vue index 65f0d1cf..c64b9f48 100644 --- a/src/components/BatchModal.vue +++ b/src/components/BatchModal.vue @@ -6,51 +6,107 @@ <ion-icon slot="icon-only" :icon="closeOutline" /> </ion-button> </ion-buttons> - <ion-title>{{ currentBatch?.jobName ? currentBatch?.jobName : $t('New broker run') }}</ion-title> + <ion-title>{{ $t('New broker run') }}</ion-title> </ion-toolbar> </ion-header> <ion-content> <ion-item> - <ion-label position="fixed">{{ $t('Batch name') }}</ion-label> + <ion-label position="fixed">{{ $t('Name') }}</ion-label> <ion-input :placeholder="currentDateTime = getCurrentDateTime()" v-model="jobName" /> </ion-item> - <ion-item :disabled="currentBatch?.jobId"> - <ion-label>{{ $t('Order queue') }}</ion-label> - <ion-select slot="end" interface="popover" :value="this.currentScheduledBatch?.facilityId || batchFacilityId" @ionChange="batchFacilityId = $event['detail'].value"> + <ion-item> + <ion-icon slot="start" :icon="ticketOutline" /> + <ion-label>{{ $t('Order parking') }}</ion-label> + <ion-select slot="end" interface="popover" :value="batchFacilityId" @ionChange="batchFacilityId = $event['detail'].value; updateCustomParameters()"> <ion-select-option value="_NA_">{{ $t("Brokering queue") }}</ion-select-option> <ion-select-option value="PRE_ORDER_PARKING">{{ $t("Pre-order parking") }}</ion-select-option> <ion-select-option value="BACKORDER_PARKING">{{ $t("Back-order parking") }}</ion-select-option> </ion-select> </ion-item> - <ion-radio-group> - <ion-item :disabled="currentBatch?.jobId"> - <ion-label>{{ $t('New orders') }}</ion-label> - <!-- "this.currentScheduledBatch?.unfillable === false" - Did this because ion-radio is not considering boolean --> - <ion-radio :checked="this.currentScheduledBatch?.unfillable === false" slot="start" @click="unfillableOrder = false" color="secondary"/> + <ion-item> + <ion-icon slot="start" :icon="warningOutline" /> + <ion-label>{{ $t('Unfillable orders') }}</ion-label> + <ion-toggle slot="end" :checked="unfillableOrder" @ionChange="unfillableOrder = !unfillableOrder; updateCustomParameters()" /> + </ion-item> + + <ion-list v-if="customOptionalParameters.length || customRequiredParameters.length"> + <ion-item lines="none"> + <ion-label>{{ $t('More parameters') }}</ion-label> + </ion-item> + + <ion-item-divider v-if="customRequiredParameters.length" color="light"> + <ion-label>{{ $t('Required Parameters') }}</ion-label> + </ion-item-divider> + + <ion-item :key="index" v-for="(parameter, index) in customRequiredParameters"> + <ion-label>{{ parameter.name }}</ion-label> + <ion-input :placeholder="parameter.value ? parameter.value : parameter.name" v-model="parameter.value" /> + <ion-note slot="helper">{{ parameter.type }}</ion-note> </ion-item> - <ion-item :disabled="currentBatch?.jobId"> - <ion-label>{{ $t('Unfillable orders') }}</ion-label> - <!-- "this.currentScheduledBatch?.unfillable === false" - Did this because ion-radio is not considering boolean --> - <ion-radio :checked="this.currentScheduledBatch?.unfillable === true" slot="start" @click="unfillableOrder = true" color="secondary"/> + + <ion-item-divider v-if="customOptionalParameters.length" color="light"> + <ion-label>{{ $t('Optional Parameters') }}</ion-label> + </ion-item-divider> + + <ion-item :key="index" v-for="(parameter, index) in customOptionalParameters"> + <ion-label>{{ parameter.name }}</ion-label> + <ion-input :placeholder="parameter.value ? parameter.value : parameter.name" v-model="parameter.value"/> + <ion-note slot="helper">{{ parameter.type }}</ion-note> </ion-item> - </ion-radio-group> - <ion-item> - <ion-label position="fixed">{{ $t("Schedule") }}</ion-label> - <ion-datetime hour-cycle="h12" :value="currentBatch?.runTime ? getDateTime(currentBatch.runTime) : getNowTimestamp()" @ionChange="updateRunTime($event)" presentation="time" size="cover" /> - </ion-item> - <ion-fab @click="updateJob()" vertical="bottom" horizontal="end" slot="fixed"> - <ion-fab-button> - <ion-icon :icon="checkmarkDoneOutline" /> - </ion-fab-button> - </ion-fab> + </ion-list> + <ion-list v-else> + <ion-item> + <ion-label>{{ $t('No parameters available') }}</ion-label> + </ion-item> + </ion-list> + + <ion-card> + <ion-card-header> + <ion-card-title>{{ $t('Schedule') }}</ion-card-title> + </ion-card-header> + + <ion-item> + <ion-icon slot="start" :icon="timeOutline" /> + <ion-label>{{ $t('Run time') }}</ion-label> + <ion-select interface="popover" :placeholder="$t('Select')" :value="runTime" @ionChange="updateRunTime($event)"> + <ion-select-option v-for="runTime in runTimes" :key="runTime.value" :value="runTime.value">{{ $t(runTime.label) }}</ion-select-option> + </ion-select> + + <ion-modal class="date-time-modal" :is-open="isDateTimeModalOpen" @didDismiss="() => isDateTimeModalOpen = false"> + <ion-content force-overscroll="false"> + <ion-datetime + show-default-buttons + hour-cycle="h23" + :value="runTime ? (isCustomRunTime(runTime) ? getDateTime(runTime) : getDateTime(DateTime.now().toMillis() + runTime)) : getNowTimestamp()" + @ionChange="updateCustomTime($event)" + /> + </ion-content> + </ion-modal> + </ion-item> + <ion-item lines="none"> + <ion-icon slot="start" :icon="timerOutline" /> + <ion-label>{{ $t('Frequency') }}</ion-label> + <ion-select :value="jobStatus" :interface-options="{ header: $t('Frequency') }" interface="popover" :placeholder="$t('Disabled')" @ionChange="jobStatus = $event.detail.value" @ionDismiss="jobStatus == 'CUSTOM' && setCustomFrequency()"> + <ion-select-option v-for="freq in frequencyOptions" :key="freq.id" :value="freq.id">{{ freq.description }}</ion-select-option> + </ion-select> + </ion-item> + </ion-card> </ion-content> + <ion-fab :disabled="!hasPermission(Actions.APP_JOB_UPDATE) || isRequiredParametersMissing" @click="updateJob()" vertical="bottom" horizontal="end" slot="fixed"> + <ion-fab-button> + <ion-icon :icon="checkmarkDoneOutline" /> + </ion-fab-button> + </ion-fab> </template> <script lang="ts"> import { IonButton, IonButtons, + IonCard, + IonCardHeader, + IonCardTitle, IonContent, IonDatetime, IonFab, @@ -59,27 +115,35 @@ import { IonIcon, IonInput, IonItem, + IonItemDivider, IonLabel, - IonRadio, - IonRadioGroup, + IonList, + IonModal, + IonNote, IonSelect, IonSelectOption, IonTitle, + IonToggle, IonToolbar, modalController } from '@ionic/vue'; import { defineComponent } from 'vue'; -import { closeOutline, checkmarkDoneOutline } from 'ionicons/icons'; +import { closeOutline, checkmarkDoneOutline, ticketOutline, timeOutline, timerOutline, warningOutline } from 'ionicons/icons'; import { mapGetters, useStore } from 'vuex'; import { DateTime } from 'luxon'; -import { handleDateTimeInput, generateJobCustomParameters, getNowTimestamp, isFutureDate, showToast, hasJobDataError } from '@/utils'; +import { handleDateTimeInput, generateAllowedFrequencies, generateAllowedRunTimes, generateJobCustomParameters, generateJobCustomOptions, getNowTimestamp, hasJobDataError, isCustomRunTime, isFutureDate, showToast } from '@/utils'; import { translate } from '@/i18n' +import CustomFrequencyModal from '@/components/CustomFrequencyModal.vue'; +import { Actions, hasPermission } from '@/authorization' export default defineComponent({ name: 'BatchModal', components: { IonButton, IonButtons, + IonCard, + IonCardHeader, + IonCardTitle, IonContent, IonDatetime, IonFab, @@ -88,15 +152,17 @@ export default defineComponent({ IonIcon, IonInput, IonItem, + IonItemDivider, IonLabel, - IonRadio, - IonRadioGroup, + IonList, + IonModal, + IonNote, IonSelect, IonSelectOption, IonTitle, + IonToggle, IonToolbar, }, - props: ["id", "enumId"], data() { return { jobEnums: JSON.parse(process.env?.VUE_APP_BATCH_JOB_ENUMS as string) as any, @@ -105,9 +171,13 @@ export default defineComponent({ unfillableOrder: false as boolean, batchFacilityId: '_NA_' as string, currentDateTime: '' as string, - jobRunTime: '' as any, - currentScheduledBatch: {} as any, - orders: "" + runTime: '' as any, + runTimes: [] as any, + isDateTimeModalOpen: false, + jobStatus: null, + frequencyOptions: [] as any, + customOptionalParameters: [] as any, + customRequiredParameters: [] as any } }, computed: { @@ -119,64 +189,107 @@ export default defineComponent({ }), }, mounted() { - this.getCurrentBatch(); + this.generateRunTimes(this.runTime) + this.generateFrequencyOptions(this.jobStatus) + this.updateCustomParameters() }, methods: { - getCurrentBatch() { - this.currentBatch = this.getJob(this.enumId)?.find((job: any) => job.id === this.id) - this.jobName = this.currentBatch?.jobName; - this.currentScheduledBatch = (this as any).jobEnums[this.currentBatch?.enumId]; - }, getDateTime(time: any) { return DateTime.fromMillis(time).toISO() }, closeModal() { modalController.dismiss({ dismissed: true }); }, - async updateJob() { - let batchJobEnum = this.enumId; - if (!batchJobEnum) { - const jobEnum: any = Object.values(this.jobEnums)?.find((job: any) => { - return job.unfillable === this.unfillableOrder && job.facilityId === this.batchFacilityId - }); - batchJobEnum = jobEnum.id + async generateRunTimes(currentRunTime?: any) { + const runTimes = JSON.parse(JSON.stringify(generateAllowedRunTimes())) + let selectedRunTime + // 0 check for the 'Now' value and '' check for initial render + if(currentRunTime || currentRunTime === 0 ) { + selectedRunTime = runTimes.some((runTime: any) => runTime.value === currentRunTime) + if(!selectedRunTime) runTimes.push({ label: this.getTime(currentRunTime), value: currentRunTime }) } + this.runTime = currentRunTime + this.runTimes = runTimes + }, + async generateFrequencyOptions(currentFrequency?: any) { + const frequencyOptions = JSON.parse(JSON.stringify(generateAllowedFrequencies())); + if(hasPermission(Actions.APP_CUSTOM_FREQ_VIEW)) frequencyOptions.push({ "id": "CUSTOM", "description": "Custom" }) + if(currentFrequency) { + const selectedFrequency = frequencyOptions.find((frequency: any) => frequency.id === currentFrequency); + if(!selectedFrequency ) { + const frequencies = await this.store.dispatch("job/fetchTemporalExpression", [ currentFrequency ]); + const frequency = frequencies[currentFrequency]; + frequency && (frequencyOptions.push({ "id": frequency.tempExprId, "description": frequency.description })) + } + } + this.frequencyOptions = frequencyOptions; + this.jobStatus = currentFrequency; + }, + async updateJob() { + const jobEnum: any = Object.values(this.jobEnums)?.find((job: any) => job.unfillable === this.unfillableOrder && job.facilityId === this.batchFacilityId); - const job = this.currentBatch ? this.currentBatch : this.getJob(batchJobEnum)?.find((job: any) => job.status === 'SERVICE_DRAFT'); - - if (!job) { + const job = this.getJob(jobEnum.id)?.find((job: any) => job.status === 'SERVICE_DRAFT'); + if(!job) { showToast(translate('Configuration missing')) return; } // return if job has missing data or error - if (hasJobDataError(job)) return; - - if (this.jobRunTime) { - job['runTime'] = this.jobRunTime - } + if(hasJobDataError(job)) return; - job['jobStatus'] = 'EVERYDAY'; - job['jobName'] = this.jobName || this.currentDateTime; + job.runTime = this.runTime != 0 ? (!isCustomRunTime(this.runTime) ? DateTime.now().toMillis() + this.runTime : this.runTime) : '' // if job runTime is not a valid date then making runTime as empty - if (job?.runTime && !isFutureDate(job?.runTime)) { + if(job?.runTime && !isFutureDate(job?.runTime)) { job.runTime = '' } - if (job?.status === 'SERVICE_DRAFT') { - const jobCustomParameters = generateJobCustomParameters([], [], job.runtimeData) - await this.store.dispatch('job/scheduleService', { job, jobCustomParameters }) - } else if (job?.status === 'SERVICE_PENDING') { - await this.store.dispatch('job/updateJob', job) - } + + job['jobStatus'] = this.jobStatus ? this.jobStatus : 'HOURLY'; + job['jobName'] = this.jobName || this.currentDateTime; + + const jobCustomParameters = generateJobCustomParameters(this.customRequiredParameters, this.customOptionalParameters, job.runtimeData) + + await this.store.dispatch('job/scheduleService', { job, jobCustomParameters }) this.closeModal() }, - updateRunTime(ev: CustomEvent) { - this.jobRunTime = handleDateTimeInput(ev['detail'].value) + async setCustomFrequency() { + const customFrequencyModal = await modalController.create({ + component: CustomFrequencyModal, + }); + customFrequencyModal.onDidDismiss() + .then((result) => { + let jobStatus = result.data.frequencyId; + this.generateFrequencyOptions(jobStatus); + }); + return customFrequencyModal.present(); + }, + updateCustomTime(event: CustomEvent) { + const currTime = DateTime.now().toMillis(); + const setTime = handleDateTimeInput(event.detail.value); + if(setTime > currTime) this.generateRunTimes(setTime) + else showToast(translate("Provide a future date and time")) + }, + updateCustomParameters() { + const jobEnum: any = Object.values(this.jobEnums)?.find((job: any) => job.unfillable === this.unfillableOrder && job.facilityId === this.batchFacilityId); + + const job = this.getJob(jobEnum.id)?.find((job: any) => job.status === 'SERVICE_DRAFT'); + this.customOptionalParameters = generateJobCustomOptions(job).optionalParameters; + this.customRequiredParameters = generateJobCustomOptions(job).requiredParameters; + }, + updateRunTime(event: CustomEvent) { + const value = event.detail.value + if(value != 'CUSTOM') this.generateRunTimes(value) + else this.isDateTimeModalOpen = true }, getCurrentDateTime() { return DateTime.now().setZone(this.userProfile.userTimeZone).toLocaleString(DateTime.DATETIME_MED); }, + getTime(time: any) { + return DateTime.fromMillis(time).toLocaleString(DateTime.DATETIME_MED); + }, + isRequiredParametersMissing() { + return this.customRequiredParameters.some((parameter: any) => !parameter.value?.trim()) + } }, setup() { const store = useStore(); @@ -184,9 +297,25 @@ export default defineComponent({ return { checkmarkDoneOutline, closeOutline, + getNowTimestamp, + hasPermission, + isCustomRunTime, store, - getNowTimestamp + ticketOutline, + timeOutline, + timerOutline, + warningOutline, + Actions, + DateTime }; }, }); -</script> \ No newline at end of file +</script> + +<style> +ion-modal.date-time-modal { + --width: 290px; + --height: 440px; + --border-radius: 8px; +} +</style> \ No newline at end of file diff --git a/src/components/JobConfiguration.vue b/src/components/JobConfiguration.vue index 41b08b43..a27be9a9 100644 --- a/src/components/JobConfiguration.vue +++ b/src/components/JobConfiguration.vue @@ -2,7 +2,7 @@ <section> <ion-item lines="none"> <!-- Adding conditional check for currentJob.jobName as currentJob is undefined when i18n runs $t --> - <h1>{{ currentJob.enumName ? currentJob.enumName : currentJob.jobName ? currentJob.jobName : '' }}</h1> + <h1>{{ isBrokerJob ? currentJob.jobName : currentJob.enumName ? currentJob.enumName : currentJob.jobName ? currentJob.jobName : '' }}</h1> <ion-badge slot="end" color="dark" v-if="currentJob?.runTime && currentJob.statusId !== 'SERVICE_DRAFT'">{{ $t("running") }} {{ timeTillJob(currentJob.runTime) }}</ion-badge> </ion-item> @@ -199,7 +199,7 @@ export default defineComponent({ this.generateRunTimes(this.runTime) this.generateFrequencyOptions(this.jobStatus) }, - props: ["status", "type"], + props: ["isBrokerJob", "status", "type"], computed: { ...mapGetters({ pinnedJobs: 'user/getPinnedJobs', diff --git a/src/locales/en.json b/src/locales/en.json index eafe5f28..4c7207d8 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -33,7 +33,6 @@ "Automatically ship orders that are packed and have a tracking number if required.": "Automatically ship orders that are packed and have a tracking number if required.", "Back-order parking": "Back-order parking", "Backorder": "Backorder", - "Batch name": "Batch name", "Batches": "Batches", "before auto cancelation": "before auto cancelation", "Before importing historical orders in bulk, make sure all products are set up or else order import will not run correctly.": "Before importing historical orders in bulk, make sure all products are set up or else order import will not run correctly.", @@ -67,6 +66,7 @@ "Custom Parameters": "Custom Parameters", "Create batches and schedule brokering for different orders.": "Create batches and schedule brokering for different orders.", "Create or update order fulfillment history records from FTP.": "Create or update order fulfillment history records from FTP.", + "Create new brokering job": "Create new brokering job", "Daily": "Daily", "Dashoard": "Dashoard", "Days": "Days", @@ -129,7 +129,9 @@ "Miscellaneous": "Miscellaneous", "Miscellaneous jobs": "Miscellaneous jobs", "More jobs": "More jobs", + "More parameters": "More parameters", "More options": "More options", + "Name": "Name", "New broker run": "New broker run", "New orders": "New orders", "New products": "New products", @@ -138,6 +140,7 @@ "No frequency found": "No frequency found", "No jobs found": "No jobs found", "No jobs have run yet": "No jobs have run yet", + "No parameters available": "No parameters available", "No previous occurrence": "No previous occurrence", "No time zone found": "No time zone found", "Notes": "Notes", @@ -149,7 +152,7 @@ "On hold": "On hold", "Open": "Open", "Optional Parameters": "Optional Parameters", - "Order queue": "Order queue", + "Order parking": "Order parking", "Order status": "Order status", "Orders": "Orders", "Order fulfillment": "Order fulfillment", @@ -213,6 +216,7 @@ "Schedule in bulk": "Schedule in bulk", "Schedule inventory hard sync": "Schedule inventory hard sync", "Schedule product sync": "Schedule product sync", + "Scheduled Job": "Scheduled Job", "Scheduler": "Scheduler", "Search jobs": "Search jobs", "Search time zones": "Search time zones", diff --git a/src/store/modules/job/actions.ts b/src/store/modules/job/actions.ts index c0225028..8f58ec00 100644 --- a/src/store/modules/job/actions.ts +++ b/src/store/modules/job/actions.ts @@ -483,7 +483,23 @@ const actions: ActionTree<JobState, RootState> = { }); // fetching temp expressions - const tempExpr = Object.values(cached).map((job: any) => job.tempExprId) + // added check to loop over cachedJob (in else part), as in case of batch job same enum contains multiple jobs as an array + // and thus tempExprId is not available by default on the root + // Before looping over cachedJob, checking whether the tempExprId is directly available on the root or not, + // if tempExprId is available on root it simply means that we have a single job against the enum + const tempExpr = Object.values(cached).reduce((tempExprIds: any, cachedJob: any) => { + if(cachedJob.tempExprId && !tempExprIds.includes(cachedJob.tempExprId)) { + tempExprIds.push(cachedJob.tempExprId) + } else if(Array.isArray(cachedJob)) { + cachedJob.map((job: any) => { + if(job.tempExprId && !tempExprIds.includes(job.tempExprId)) { + tempExprIds.push(job.tempExprId) + } + }); + } + + return tempExprIds; + }, []) await dispatch('fetchTemporalExpression', tempExpr) commit(types.JOB_UPDATED_BULK, cached); diff --git a/src/views/Brokering.vue b/src/views/Brokering.vue index 1f8149b7..f51d6cfa 100644 --- a/src/views/Brokering.vue +++ b/src/views/Brokering.vue @@ -10,51 +10,55 @@ <ion-content> <main> <section> + <ion-button expand="block" :disabled="!hasPermission(Actions.APP_JOB_UPDATE)" @click="addBatch()">{{ $t('Create new brokering job') }}</ion-button> + + <ion-item lines="none"> + <ion-label> + <h1>{{ $t('Scheduled Job') }}</h1> + </ion-label> + </ion-item> + <ion-card> - <ion-card-header> - <ion-card-title>{{ $t("Routing") }}</ion-card-title> - </ion-card-header> <ion-item @click="viewJobConfiguration({ id: 'REJ_ORDR', status: getJobStatus(jobEnums['REJ_ORDR'])})" detail button> <ion-label class="ion-text-wrap">{{ $t("Rejected orders") }}</ion-label> <ion-label slot="end">{{ getTemporalExpression('REJ_ORDR') }}</ion-label> </ion-item> - <ion-item-divider> - <ion-label class="ion-text-wrap">{{ $t("Batches") }}</ion-label> - <ion-button :disabled="!hasPermission(Actions.APP_JOB_UPDATE)" fill="clear" @click="addBatch()" slot="end"> - {{ $t("Add") }} - <ion-icon :icon="addCircleOutline" slot="end" /> - </ion-button> - </ion-item-divider> + </ion-card> - <ion-list ref="slidingOptions"> - <ion-item-sliding v-for="batch in orderBatchJobs" :key="batch?.id" detail v-show="batch?.status === 'SERVICE_PENDING'"> - <ion-item @click="hasPermission(Actions.APP_JOB_UPDATE) && editBatch(batch.id, batch.systemJobEnumId)" button> - <ion-label class="ion-text-wrap">{{ batch?.jobName }}</ion-label> - <ion-note slot="end">{{ batch?.runTime ? getTime(batch.runTime) : '' }}</ion-note> - </ion-item> - <ion-item-options side="start"> - <ion-item-option @click="hasPermission(Actions.APP_JOB_UPDATE) && skipBatch(batch)" color="secondary"> - <ion-icon slot="icon-only" :icon="arrowRedoOutline" /> - </ion-item-option> - </ion-item-options> - <ion-item-options side="end"> - <ion-item-option @click="hasPermission(Actions.APP_JOB_UPDATE) && deleteBatch(batch)" color="danger"> - <ion-icon slot="icon-only" :icon="trashOutline" /> - </ion-item-option> - </ion-item-options> - </ion-item-sliding> + <ion-card :button="isDesktop" v-for="batch in batchJobs()" :key="batch?.id" detail v-show="batch?.status === 'SERVICE_PENDING'" @click="hasPermission(Actions.APP_JOB_UPDATE) && viewJobConfiguration({ id: batch.enumId, job: batch })"> + <ion-card-header> + <div> + <ion-card-subtitle>{{ getBrokerQueue(batch) }}</ion-card-subtitle> + <ion-card-title>{{ batch?.jobName }}</ion-card-title> + </div> + <ion-badge class="ion-margin-start" color="dark" v-if="batch.status === 'SERVICE_PENDING'">{{ timeFromNow(batch?.runTime) }}</ion-badge> + </ion-card-header> + + <ion-list> + <ion-item lines="none"> + <ion-label class="ion-text-wrap">{{ batch?.temporalExpression?.description }}</ion-label> + <ion-label slot="end">{{ batch?.runTime ? getTime(batch.runTime) : "-" }}</ion-label> + </ion-item> + + <ion-item lines="none"> + <ion-text class="ion-text-wrap">{{ $t("Unfillable orders") }}</ion-text> + <ion-toggle disabled :checked="batchJobEnums[batch?.enumId].unfillable" slot="end"/> + </ion-item> + + <ion-item lines="none" v-if="batch?.status === 'SERVICE_PENDING' && Object.keys(generateCustomParameters(batch)).length"> + <ion-row> + <ion-chip disabled outline :key="index" v-for="(value, name, index) in generateCustomParameters(batch)"> + {{ name }}: {{ value }} + </ion-chip> + </ion-row> + </ion-item> </ion-list> - <ion-item lines="none"> - <ion-label class="ion-text-wrap"> - <p>{{ $t("Create batches and schedule brokering for different orders.") }}</p> - </ion-label> - </ion-item> </ion-card> <MoreJobs v-if="getMoreJobs({...jobEnums, ...initialLoadJobEnums}, enumTypeId).length" :jobs="getMoreJobs({...jobEnums, ...initialLoadJobEnums}, enumTypeId)" /> </section> <aside class="desktop-only" v-if="isDesktop" v-show="currentJob"> - <JobConfiguration :status="currentJobStatus" :type="freqType" :key="currentJob"/> + <JobConfiguration :status="currentJobStatus" :type="freqType" :key="currentJob" :isBrokerJob="orderBatchJobs.includes(currentJob) ? true : false"/> </aside> </main> </ion-content> @@ -63,31 +67,29 @@ <script lang="ts"> import { - alertController, + IonBadge, IonButton, IonCard, IonCardHeader, + IonCardSubtitle, IonCardTitle, + IonChip, IonContent, IonHeader, - IonIcon, IonItem, - IonItemDivider, - IonItemSliding, IonLabel, IonList, IonMenuButton, - IonNote, - IonItemOption, - IonItemOptions, IonPage, + IonRow, + IonText, IonTitle, + IonToggle, IonToolbar, isPlatform, modalController } from '@ionic/vue'; import { defineComponent } from 'vue'; -import { translate } from '@/i18n' import { addCircleOutline, arrowRedoOutline, trashOutline } from 'ionicons/icons'; import BatchModal from '@/components/BatchModal.vue'; import { useStore } from "@/store"; @@ -95,7 +97,7 @@ import { useRouter } from 'vue-router' import { mapGetters } from "vuex"; import JobConfiguration from '@/components/JobConfiguration.vue'; import { DateTime } from 'luxon'; -import { hasError, isFutureDate, showToast } from '@/utils'; +import { generateJobCustomOptions, generateJobCustomParameters, isFutureDate } from '@/utils'; import emitter from '@/event-bus'; import MoreJobs from '@/components/MoreJobs.vue'; import { Actions, hasPermission } from '@/authorization' @@ -103,24 +105,24 @@ import { Actions, hasPermission } from '@/authorization' export default defineComponent({ name: 'Brokering', components: { + IonBadge, IonButton, IonCard, IonCardHeader, + IonCardSubtitle, IonCardTitle, + IonChip, IonContent, IonHeader, - IonIcon, IonItem, - IonItemSliding, - IonItemDivider, IonLabel, IonList, IonMenuButton, - IonNote, - IonItemOption, - IonItemOptions, IonPage, + IonRow, + IonText, IonTitle, + IonToggle, IonToolbar, JobConfiguration, MoreJobs @@ -149,76 +151,38 @@ export default defineComponent({ }), }, methods: { - async addBatch() { - const batchmodal = await modalController.create({ - component: BatchModal - }); - return batchmodal.present(); + batchJobs() { + return this.orderBatchJobs?.sort((jobA: any,jobB: any) => jobA.runTime - jobB.runTime) }, - async editBatch(id: string, enumId: string) { + async addBatch() { const batchmodal = await modalController.create({ component: BatchModal, - componentProps: {id, enumId} + breakpoints: [0, 0.25, 0.5, 0.75, 1], + initialBreakpoint: 1 }); return batchmodal.present(); }, - async deleteBatch(batch: any) { - const deleteBatchAlert = await alertController - .create({ - header: this.$t('Cancel job'), - message: this.$t("Canceling this job will cancel this occurrence and all following occurrences. This job will have to be re-enabled manually to run it again."), - buttons: [ - { - text: this.$t("Don't cancel"), - role: 'cancel', - }, - { - text: this.$t("Cancel"), - handler: async () => { - await this.store.dispatch('job/cancelJob', batch); - }, - }, - ], - }); - return deleteBatchAlert.present(); - }, - async skipBatch (batch: any) { - const skipJobAlert = await alertController - .create({ - header: this.$t('Skip job'), - message: this.$t('Skipping will run this job at the next occurrence based on the temporal expression.'), - buttons: [ - { - text: this.$t("Don't skip"), - role: 'cancel', - }, - { - text: this.$t('Skip'), - handler: async () => { - this.store.dispatch('job/skipJob', batch).then((resp) => { - if (resp.status === 200 && !hasError(resp)) { - showToast(translate("This job has been skipped")); - } else { - showToast(translate("This job schedule cannot be skipped")); - } - }); - (this as any).$refs.slidingOptions.$el.closeSlidingItems(); - }, - } - ] - }); - return skipJobAlert.present(); + getTime(time: any) { + return DateTime.fromMillis(time).toFormat('hh:mm a'); }, - getTime (time: any) { - return DateTime.fromMillis(time).toLocaleString(DateTime.DATETIME_MED); + getBrokerQueue(job: any) { + const brokerQueueId = this.batchJobEnums[job?.enumId].facilityId + + if(brokerQueueId === "_NA_") { + return "Brokering queue" + } else if(brokerQueueId === "PRE_ORDER_PARKING") { + return "Pre-order parking" + } else { + return "Back-order parking" + } }, async viewJobConfiguration(jobInformation: any) { this.currentJob = jobInformation.job || this.getJob(this.jobEnums[jobInformation.id]) - this.currentJobStatus = jobInformation.status + this.currentJobStatus = this.currentJob?.statusId === 'SERVICE_DRAFT' ? 'SERVICE_DRAFT' : this.currentJob?.frequency this.freqType = jobInformation.id && this.jobFrequencyType[jobInformation.id] // if job runTime is not a valid date then making runTime as empty - if (this.currentJob?.runTime && !isFutureDate(this.currentJob?.runTime)) { + if(this.currentJob?.runTime && !isFutureDate(this.currentJob?.runTime)) { this.currentJob.runTime = '' } @@ -227,7 +191,7 @@ export default defineComponent({ this.router.push({ name: 'JobDetails', params: { jobId: this.currentJob.jobId, category: "orders" } }); return; } - if (this.currentJob && !this.isJobDetailAnimationCompleted) { + if(this.currentJob && !this.isJobDetailAnimationCompleted) { emitter.emit('playAnimation'); this.isJobDetailAnimationCompleted = true; } @@ -237,7 +201,7 @@ export default defineComponent({ this.getTemporalExpr(this.getJobStatus(this.jobEnums[enumId]))?.description : this.$t('Disabled') }, - async fetchJobs(){ + async fetchJobs() { await this.store.dispatch("job/fetchJobs", { "inputFields": { // If we fetch broker sys job by not passing systemJobEnumId filter then this api @@ -255,8 +219,19 @@ export default defineComponent({ } }); }, + timeFromNow(time: any) { + const timeDiff = DateTime.fromMillis(time).diff(DateTime.local()); + return DateTime.local().plus(timeDiff).toRelative(); + }, + generateCustomParameters(job: any) { + let customOptionalParameters = generateJobCustomOptions(job).optionalParameters; + let customRequiredParameters = generateJobCustomOptions(job).requiredParameters; + + // passing runTimeData params as empty, as we don't need to show the runTimeData information on UI + return generateJobCustomParameters(customRequiredParameters, customOptionalParameters, {}) + } }, - mounted () { + mounted() { this.fetchJobs(); emitter.on("productStoreOrConfigChanged", this.fetchJobs); emitter.on('viewJobConfiguration', this.viewJobConfiguration) @@ -280,4 +255,12 @@ export default defineComponent({ }; }, }); -</script> \ No newline at end of file +</script> + +<style scoped> +ion-card-header { + display: flex; + justify-content: space-between; + align-items: center; +} +</style> \ No newline at end of file