Files
raven-client/ayanova/src/views/home-notify-subscription.vue
2021-05-04 00:29:06 +00:00

774 lines
27 KiB
Vue

<template>
<div>
<v-row v-if="formState.ready">
<v-col>
<v-form ref="form">
<v-row>
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
<!-- {{ obj }} -->
<v-col cols="12" sm="6" lg="4" xl="3">
<v-select
v-model="obj.eventType"
:items="selectLists.eventTypes"
item-text="name"
item-value="id"
:readonly="formState.readOnly"
:label="$ay.t('NotifyEventType')"
ref="eventType"
data-cy="eventType"
:rules="[form().integerValid(this, 'eventType')]"
:error-messages="form().serverErrors(this, 'eventType')"
@input="fieldValueChanged('eventType')"
></v-select>
</v-col>
<v-col v-if="showWoStatus" cols="12" sm="6" lg="4" xl="3">
todo: idValue here for wostatus
</v-col>
<v-col v-if="showQuoteStatus" cols="12" sm="6" lg="4" xl="3">
todo: idValue here for quotestatus
</v-col>
<v-col v-if="showDecValue" cols="12" sm="6" lg="4" xl="3">
todo: decValue here for "The Andy"
</v-col>
<v-col v-if="showAgeValue" cols="12" sm="6" lg="4" xl="3">
<gz-duration-picker
v-model="obj.ageValue"
:readonly="formState.readOnly"
:label="$ay.t('Duration')"
:show-seconds="false"
ref="ageValue"
data-cy="ageValue"
:error-messages="form().serverErrors(this, 'ageValue')"
@input="fieldValueChanged('ageValue')"
></gz-duration-picker>
</v-col>
<v-col v-if="showAyaType" cols="12" sm="6" lg="4" xl="3">
<v-select
v-model="obj.ayaType"
:items="selectLists.coreAyaTypes"
item-text="name"
item-value="id"
:readonly="formState.readOnly"
:label="$ay.t('AyaType')"
ref="ayaType"
data-cy="ayaType"
:rules="[form().integerValid(this, 'ayaType')]"
:error-messages="form().serverErrors(this, 'ayaType')"
@input="fieldValueChanged('ayaType')"
></v-select>
</v-col>
<v-col v-if="showAdvanceNotice" cols="12" sm="6" lg="4" xl="3">
<gz-duration-picker
v-model="obj.advanceNotice"
:readonly="formState.readOnly"
:label="$ay.t('NotifySubscriptionPendingSpan')"
:show-seconds="false"
ref="advanceNotice"
data-cy="advanceNotice"
:error-messages="form().serverErrors(this, 'advanceNotice')"
@input="fieldValueChanged('advanceNotice')"
></gz-duration-picker>
</v-col>
<v-col v-if="showDeliveryMethod" cols="12" sm="6" lg="4" xl="3">
<v-select
v-model="obj.deliveryMethod"
:items="selectLists.deliveryMethods"
item-text="name"
item-value="id"
:readonly="formState.readOnly"
:label="$ay.t('NotifyDeliveryMethod')"
ref="deliveryMethod"
data-cy="deliveryMethod"
:rules="[form().integerValid(this, 'deliveryMethod')]"
:error-messages="form().serverErrors(this, 'deliveryMethod')"
@input="fieldValueChanged('deliveryMethod')"
></v-select>
</v-col>
<v-col v-if="showDeliveryAddress" cols="12" sm="6" lg="4" xl="3">
<v-text-field
v-model="obj.deliveryAddress"
:readonly="formState.readOnly"
:clearable="!formState.readOnly"
@click:clear="fieldValueChanged('deliveryAddress')"
:label="$ay.t('NotifyDeliveryAddress')"
:rules="[form().required(this, 'deliveryAddress')]"
:error-messages="form().serverErrors(this, 'deliveryAddress')"
ref="deliveryAddress"
data-cy="deliveryAddress"
@input="fieldValueChanged('deliveryAddress')"
></v-text-field>
</v-col>
<v-col v-if="showLinkReportId" cols="12" sm="6" lg="4" xl="3">
ATTACH REPORT todo: dynamic list of reports to select from for
type chosen
</v-col>
<v-col v-if="showTags" cols="12">
<gz-tag-picker
v-model="obj.tags"
:label="$ay.t('TaggedWith')"
:readonly="formState.readOnly"
ref="tags"
data-cy="tags"
:error-messages="form().serverErrors(this, 'tags')"
@input="fieldValueChanged('tags')"
></gz-tag-picker>
</v-col>
</v-row>
</v-form>
</v-col>
</v-row>
<template v-if="!formState.ready">
<v-progress-circular
indeterminate
color="primary"
:size="60"
></v-progress-circular>
</template>
</div>
</template>
<script>
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/* Xeslint-disable */
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
const FORM_KEY = "home-notify-subscription";
const API_BASE_URL = "notify-subscription/";
export default {
async created() {
//created is called when the route is updated to show a new record even though we don't need to re-init again
let vm = this;
try {
await initForm(vm);
vm.rights = window.$gz.role.getRights(window.$gz.type.NotifySubscription);
vm.formState.readOnly = !vm.rights.change;
window.$gz.eventBus.$on("menu-click", clickHandler);
//id 0 means create a new record don't load one
if (vm.$route.params.recordid != 0) {
//is there already an obj from a prior operation?
if (this.$route.params.obj) {
//yes, no need to fetch it
this.obj = this.$route.params.obj;
vm.formState.loading = false; //here we handle it immediately
} else {
await vm.getDataFromApi(vm.$route.params.recordid); //let getdata handle loading
}
} else {
//default to their configured email address on new record
this.obj.deliveryAddress = this.$store.state.userOptions.emailAddress;
vm.formState.loading = false; //here we handle it immediately
}
//set initial form status
window.$gz.form.setFormState({
vm: vm,
dirty: false,
valid: true
});
generateMenu(vm);
} catch (error) {
window.$gz.errorHandler.handleFormError(error, vm);
} finally {
vm.formState.ready = true;
}
},
async beforeRouteLeave(to, from, next) {
if (!this.formState.dirty || JUST_DELETED) {
next();
return;
}
if ((await window.$gz.dialog.confirmLeaveUnsaved()) === true) {
next();
} else {
next(false);
}
},
beforeDestroy() {
window.$gz.eventBus.$off("menu-click", clickHandler);
},
data() {
return {
selectLists: {
eventTypes: [],
deliveryMethods: [],
coreAyaTypes: []
},
/* public long Id { get; set; }
public uint Concurrency { get; set; }
[Required]
public long UserId { get; set; }
public AyaType? AyaType { get; set; }
[Required]
public NotifyEventType EventType { get; set; }
public TimeSpan? AdvanceNotice { get; set; } //Note: I've been doing nullable wrong sort of: https://stackoverflow.com/a/29149207/8939
public long? IdValue { get; set; }
public decimal? DecValue { get; set; }
[Required]
public NotifyDeliveryMethod DeliveryMethod { get; set; }
public string DeliveryAddress { get; set; }
public long? LinkReportId { get; set; }
public List<string> tags { get; set; }*/
obj: {
id: 0,
concurrency: 0,
userId: this.$store.state.userId,
ayaType: 0,
eventType: 0,
advanceNotice: "00:00:00",
idValue: 0,
decValue: 0,
deliveryMethod: 0,
deliveryAddress: null,
linkReportId: 0,
tags: []
},
formState: {
ready: false,
dirty: false,
valid: true,
readOnly: false,
loading: true,
errorBoxMessage: null,
appError: null,
serverError: {}
},
rights: window.$gz.role.defaultRightsObject(),
ayaType: window.$gz.type.NotifySubscription
};
},
//WATCHERS
watch: {
formState: {
handler: function(val) {
if (this.formState.loading) {
return;
}
//enable / disable save button
if (val.dirty && val.valid && !val.readOnly) {
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":save");
} else {
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":save");
}
//enable / disable duplicate / new button
if (!val.dirty && val.valid && !val.readOnly) {
window.$gz.eventBus.$emit(
"menu-enable-item",
FORM_KEY + ":duplicate"
);
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":new");
} else {
window.$gz.eventBus.$emit(
"menu-disable-item",
FORM_KEY + ":duplicate"
);
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":new");
}
},
deep: true
}
},
computed: {
showDeliveryAddress() {
return this.obj.deliveryMethod == 2;
},
showDeliveryMethod() {
return this.obj.eventType != 27;
},
showWoStatus() {
switch (this.obj.eventType) {
case 4:
case 24:
return true;
default:
return false;
}
},
showQuoteStatus() {
return this.obj.eventType == 9;
},
showDecValue() {
return this.obj.eventType == 23 || this.obj.eventType == 11; //service bank threshold
},
showAyaType() {
switch (this.obj.eventType) {
case 1:
case 2:
case 3:
case 10:
return true;
default:
return false;
}
},
showAdvanceNotice() {
switch (this.obj.eventType) {
case 5:
case 12:
case 14:
case 15:
case 16:
case 21:
case 24: //Actually, this is workorder status age deadman notification, the title would be different for the advance notice thing, maybe its' own control?
case 25:
return true;
default:
return false;
}
},
showAgeValue() {
switch (this.obj.eventType) {
case 24:
case 10:
case 32:
return true;
default:
return false;
}
},
showLinkReportId() {
//NOTE: Currently projected: Only Quotes and Workorders are involved in sending as attached reports
//And only to customers and only single items, not lists so it's doable to indicate a report id
//and send a link from notification to open the report
switch (this.obj.eventType) {
case 999: //to be filled in later
return true;
default:
return false;
}
},
showTags() {
switch (this.obj.eventType) {
case 27: //General notification
case 20: //backup status
case 31: //workordercreatedforcustomer customer notification, no tags
return false;
default:
return true;
}
}
},
methods: {
canSave: function() {
return this.formState.valid && this.formState.dirty;
},
canDuplicate: function() {
return this.formState.valid && !this.formState.dirty;
},
ayaTypes: function() {
return window.$gz.type;
},
form() {
return window.$gz.form;
},
fieldValueChanged(ref) {
if (
this.formState.ready &&
!this.formState.loading &&
!this.formState.readOnly
) {
//"fieldValueChanged('eventType')"
//if it's a general notification make sure delivery is set to email
if (ref == "eventType" && this.obj.eventType == 27) {
this.obj.deliveryMethod = 2;
}
window.$gz.form.fieldValueChanged(this, ref);
}
},
async getDataFromApi(recordId) {
let vm = this;
vm.formState.loading = true;
if (!recordId) {
throw new Error(FORM_KEY + "::getDataFromApi -> Missing recordID!");
}
let url = API_BASE_URL + recordId;
try {
window.$gz.form.deleteAllErrorBoxErrors(vm);
let res = await window.$gz.api.get(url);
if (res.error) {
//Not found?
if (res.error.code == "2010") {
window.$gz.form.handleObjectNotFound(vm);
}
vm.formState.serverError = res.error;
window.$gz.form.setErrorBoxErrors(vm);
vm.formState.loading = false;
} else {
vm.obj = res.data;
//modify the menu as necessary
generateMenu(vm);
//Update the form status
window.$gz.form.setFormState({
vm: vm,
dirty: false,
valid: true,
loading: false,
readOnly: res.data.eventType == 27 && res.data.deliveryMethod == 1 //default in-app notification is read only
});
}
} catch (error) {
window.$gz.errorHandler.handleFormError(error, vm);
vm.formState.loading = false;
}
},
async submit() {
let vm = this;
if (vm.canSave == false) {
return;
}
try {
vm.formState.loading = true;
let url = API_BASE_URL;
//clear any errors vm might be around from previous submit
window.$gz.form.deleteAllErrorBoxErrors(vm);
//add in password and login if changed
let submitObject = vm.obj;
if (vm.password != null && vm.password != "") {
submitObject.password = vm.password;
}
if (vm.login != null && vm.login != "") {
submitObject.login = vm.login;
}
if (submitObject.roles == null) {
submitObject.roles = 0;
}
let res = await window.$gz.api.upsert(url, submitObject);
if (res.error) {
vm.formState.serverError = res.error;
window.$gz.form.setErrorBoxErrors(vm);
} else {
//Logic for detecting if a post or put: if id then it was a post, if no id then it was a put
if (res.data.id) {
//POST - whole new object returned
vm.obj = res.data;
//Change URL to new record
//NOTE: will not cause a page re-render, almost nothing does unless forced with a KEY property or using router.GO()
this.$router.push({
name: "home-notify-subscription",
params: {
recordid: res.data.id,
obj: res.data //Pass data object to new form
}
});
} else {
//PUT - only concurrency token is returned (**warning, if server changes object other fields then this needs to act more like POST above but is more efficient this way**)
//Handle "put" of an existing record (UPDATE)
vm.obj.concurrency = res.data.concurrency;
}
//Update the form status
window.$gz.form.setFormState({
vm: vm,
dirty: false,
valid: true
});
}
} catch (ex) {
window.$gz.errorHandler.handleFormError(ex, vm);
} finally {
vm.formState.loading = false;
}
},
async remove() {
let vm = this;
try {
let dialogResult = await window.$gz.dialog.confirmDelete();
if (dialogResult != true) {
return;
}
//do the delete
vm.formState.loading = true;
//No need to delete a new record, just abandon it...
if (vm.$route.params.recordid == 0) {
//this should not get offered for delete but to be safe and clear just in case:
JUST_DELETED = true;
// navigate backwards
vm.$router.go(-1);
} else {
let url = API_BASE_URL + vm.$route.params.recordid;
window.$gz.form.deleteAllErrorBoxErrors(vm);
let res = await window.$gz.api.remove(url);
if (res.error) {
vm.formState.serverError = res.error;
window.$gz.form.setErrorBoxErrors(vm);
} else {
//workaround to prevent warning about leaving dirty record
//For some reason I couldn't just reset isdirty in formstate
JUST_DELETED = true;
// navigate backwards
vm.$router.go(-1);
}
}
} catch (ex) {
window.$gz.errorHandler.handleFormError(ex, vm);
} finally {
vm.formState.loading = false;
}
},
async duplicate() {
let vm = this;
if (!vm.canDuplicate || vm.$route.params.recordid == 0) {
return;
}
vm.formState.loading = true;
let url = API_BASE_URL + "duplicate/" + vm.$route.params.recordid;
try {
window.$gz.form.deleteAllErrorBoxErrors(vm);
let res = await window.$gz.api.upsert(url);
if (res.error) {
vm.formState.serverError = res.error;
window.$gz.form.setErrorBoxErrors(vm);
} else {
//Navigate to new record
this.$router.push({
name: "home-notify-subscription",
params: {
recordid: res.data.id,
obj: res.data // pass data object to new form
}
});
}
} catch (ex) {
window.$gz.errorHandler.handleFormError(ex, vm);
} finally {
vm.formState.loading = false;
}
}
}
};
/////////////////////////////
//
//
async function clickHandler(menuItem) {
if (!menuItem) {
return;
}
let m = window.$gz.menu.parseMenuItem(menuItem);
if (m.owner == FORM_KEY && !m.disabled) {
switch (m.key) {
case "save":
m.vm.submit();
break;
case "delete":
m.vm.remove();
break;
case "new":
m.vm.$router.push({
name: "home-notify-subscription",
params: { recordid: 0, new: true }
});
break;
case "duplicate":
m.vm.duplicate();
break;
// case "report":
// if (m.id != null) {
// //last report selected
// m.vm.$router.push({
// name: "ay-report",
// params: { recordid: m.id, ayatype: window.$gz.type.NotifySubscription }
// });
// } else {
// //general report selector chosen
// let res = await m.vm.$refs.reportSelector.open();
// //if null for no selection
// //just bail out
// if (res == null) {
// return;
// }
// //persist last report selected
// window.$gz.form.setLastReport(FORM_KEY, res);
// //Now open the report viewer...
// m.vm.$router.push({
// name: "ay-report",
// params: { recordid: res.id, ayatype: window.$gz.type.NotifySubscription }
// });
// }
// break;
default:
window.$gz.eventBus.$emit(
"notify-warning",
FORM_KEY + "::context click: [" + m.key + "]"
);
}
}
}
//////////////////////
//
//
function generateMenu(vm) {
let menuOptions = {
isMain: false,
readOnly: vm.formState.readOnly,
icon: "$ayiBullhorn",
title: "NotifySubscription",
helpUrl: "home-notify-subscriptions",
formData: {
ayaType: window.$gz.type.NotifySubscription,
recordId: vm.$route.params.recordid
},
menuItems: []
};
if (vm.rights.change) {
menuOptions.menuItems.push({
title: "Save",
icon: "$ayiSave",
surface: true,
key: FORM_KEY + ":save",
vm: vm
});
}
if (vm.rights.delete && vm.$route.params.recordid != 0) {
menuOptions.menuItems.push({
title: "Delete",
icon: "$ayiTrashAlt",
surface: false,
key: FORM_KEY + ":delete",
vm: vm
});
}
if (vm.rights.change) {
menuOptions.menuItems.push({
title: "New",
icon: "$ayiPlus",
key: FORM_KEY + ":new",
vm: vm
});
}
if (vm.rights.change && vm.$route.params.recordid != 0) {
menuOptions.menuItems.push({
title: "Duplicate",
icon: "$ayiClone",
key: FORM_KEY + ":duplicate",
vm: vm
});
}
menuOptions.menuItems.push({ divider: true, inset: false });
window.$gz.eventBus.$emit("menu-change", menuOptions);
}
let JUST_DELETED = false;
/////////////////////////////////
//
//
async function initForm(vm) {
await fetchTranslatedText(vm);
// await window.$gz.formCustomTemplate.get(FORM_CUSTOM_TEMPLATE_KEY, vm);
await populateSelectionLists(vm);
}
//////////////////////////////////////////////////////////
//
// Ensures UI translated text is available
//
async function fetchTranslatedText(vm) {
await window.$gz.translation.cacheTranslations([
"NotifySubscriptionPendingSpan",
"NotifySubscription",
"NotifyDeliveryMethod",
"NotifyEventType",
"NotifyDeliveryAddress",
"tags",
"Duration",
"TaggedWith"
]);
}
//////////////////////
//
//
async function populateSelectionLists(vm) {
//ensure the pick lists required are pre-fetched
await window.$gz.enums.fetchEnumList("NotifyEventType");
let tempEventTypes = window.$gz.enums.getSelectionList("NotifyEventType");
if (window.$gz.store.getters.isOutsideUser) {
/* //see core-notifications.txt spec doc for a bit more info on each type (they are named a little bit differently)
//#### NOTE: once event is NOTED IN COMMENT (not necessarily coded yet as some can't be yet) in NotifyEventProcessor I'll mark it with a * in the comment so I know if I miss any
ObjectDeleted = 1,//* Deletion of any object of conditional specific AyaType and optionally conditional tags
ObjectCreated = 2,//* creation of any object of conditional specific AyaType and optionally conditional tags
ObjectModified = 3,//* Modification / update of any kind of any object of conditional specific AyaType and optionally conditional tags
WorkorderStatusChange = 4,//* Workorder object, any *change* of status including from no status (new) to a specific conditional status ID value
ContractExpiring = 5,//* Contract object, aged notification with optional advance notice for expiration date of contract. Customer version and User version deliveries possible.
CSRAccepted = 6,//*CustomerServiceRequest object, saved with ACCEPTED status, delivered to Customer only
CSRRejected = 7,//*CustomerServiceRequest object, saved with REJECTED status, delivered to Customer only
CopyOfCustomerNotification = 8,//*User notification of what was sent to a Customer as a notification. In other words can subscribe to any customer notifications and be sent a copy of them. Tag filter is based on Customer's tags for this one so user can choose which customer group to subscribe to this or all.
QuoteStatusChange = 9,//* Quote object, any *change* of status including from no status (new) to a specific conditional status ID value
ObjectAge = 10,//* Any object, Age (conditional on AgeValue) after creation event of any object of conditional specific AyaType and optionally conditional tags
ServiceBankDepleted = 11,//*ServiceBank object, any change to balance triggers this check, conditional on decvalue as remaining balance left to trigger this notification
ReminderImminent = 12,//*Reminder object, Advance notice setting tag conditional
ScheduledOnWorkorder = 13,//*Workorder / WorkorderItemScheduledUser object, instant notification when current user is scheduled on a service workorder
ScheduledOnWorkorderImminent = 14,//*Workorder / WorkorderItemScheduledUser object, advanced (settable) notification when current user's scheduled date/time is imminent
WorkorderFinishStatusOverdue = 15,//* Workorder object not set to a "Finished" flagged workorder status type in selected time span from creation of workorderWorkorderSetToFinishedStatus
OutsideServiceOverdue = 16,//* Workorder object , WorkorderItemOutsideService created / updated, sets advance notice on due date tag filterable
OutsideServiceReceived = 17,//* Workorder object , WorkorderItemOutsideService updated, instant notification when item received, tag filterable
PartRequestReceived = 18,//* Workorder object / workorderitempartrequest updated, sent to person who requested when parts received back
NotifyHealthCheck = 19,//* NO OBJECT, direct subscription to receive recurring daily notify system "ping" sent out between 8am and 10am once every 24 hours minimum every day server local time
BackupStatus = 20,//* NO OBJECT, direct subscription to receive results of last backup operation
CustomerServiceImminent = 21,//* Workorder / WorkorderItemScheduledUser object, notice that scheduled service is due, can set advance notice, CUSTOMER gets delivery
PartRequested = 22,//* Workorder object / workorderitempartrequest created tag filterable
WorkorderTotalExceedsThreshold = 23,//* "the Andy" Workorder updated / created, based on balance total so conditional on DecValue
WorkorderStatusAge = 24,//* Workorder object Created / Updated, conditional on exact status selected IdValue, Tags conditional, advance notice can be set
UnitWarrantyExpiry = 25,//* Unit object created, advance notice can be used, tag conditional
UnitMeterReadingMultipleExceeded = 26,//* UnitMeterReading object, Created, conditional on DecValue as the Multiple threshold, if passed then notifies
GeneralNotification = 27,//* NO OBJECT old quick notification, refers now to any direct text notification internal or user to user used for system notifications (default delivers in app but user can opt to also get email)
ServerOperationsProblem = 28,//* NO OBJECT and serious issue with server operations requiring intervention,
QuoteStatusAge = 29,//* Quote object Created / Updated, conditional on exact status selected IdValue, Tags conditional, advance notice can be set
WorkorderFinished = 30, //*Service work order is set to any status that is flagged as a "Finished" type of status
WorkorderCreatedForCustomer = 31, //*Service work order is created for Customer, only applies to that customer user notify sub for that customer, customer id is in conditional ID value for subscription
WorkorderFinishedFollowUp = 32 //* Service workorder closed status follow up again after this many TIMESPAN
*/
vm.selectLists.eventTypes = tempEventTypes.filter(
z => z.id == 6 || z.id == 7 || z.id == 21 || z.id == 30 || z.id == 31
);
} else {
vm.selectLists.eventTypes = tempEventTypes.filter(
z => z.id != 6 && z.id != 7 && z.id != 21 && z.id != 31
);
}
await window.$gz.enums.fetchEnumList("NotifyDeliveryMethod");
vm.selectLists.deliveryMethods = window.$gz.enums.getSelectionList(
"NotifyDeliveryMethod"
);
await window.$gz.enums.fetchEnumList("coreview");
vm.selectLists.coreAyaTypes = window.$gz.enums.getSelectionList("coreview");
}
</script>