1081 lines
30 KiB
Vue
1081 lines
30 KiB
Vue
<template>
|
|
<div>
|
|
<gz-report-selector ref="reportSelector"></gz-report-selector>
|
|
<div v-if="formState.ready">
|
|
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
|
<v-form ref="form">
|
|
<GzWoHeader
|
|
v-model="obj"
|
|
:form-key="formCustomTemplateKey"
|
|
:readonly="formState.readOnly"
|
|
:pvm="this"
|
|
data-cy="woHeader"
|
|
@change="setDirty()"
|
|
/>
|
|
<GzWoItems
|
|
v-model="obj"
|
|
:form-key="formCustomTemplateKey"
|
|
:readonly="formState.readOnly"
|
|
:pvm="this"
|
|
data-cy="woItems"
|
|
@change="setDirty()"
|
|
@graph-item-deleted="flagGraphItemForDelete($event)"
|
|
/>
|
|
</v-form>
|
|
</div>
|
|
<template v-if="!formState.ready">
|
|
<v-progress-circular
|
|
indeterminate
|
|
color="primary"
|
|
:size="60"
|
|
></v-progress-circular>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
/* Xeslint-disable */
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
|
|
import GzWoHeader from "../components/work-order-header.vue";
|
|
import GzWoItems from "../components/work-order-items.vue";
|
|
|
|
const FORM_KEY = "workorder-edit";
|
|
const API_BASE_URL = "workorder/";
|
|
const FORM_CUSTOM_TEMPLATE_KEY = "WorkOrder"; //<-- Should always be CoreBizObject AyaType name here where possible
|
|
|
|
export default {
|
|
components: {
|
|
GzWoHeader,
|
|
GzWoItems
|
|
},
|
|
async created() {
|
|
const vm = this;
|
|
|
|
try {
|
|
await initForm(vm);
|
|
|
|
//set base rights
|
|
vm.rights = window.$gz.role.getRights(window.$gz.type.WorkOrder);
|
|
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;
|
|
|
|
window.$gz.form.setFormState({
|
|
vm: vm,
|
|
loading: false
|
|
});
|
|
} else {
|
|
await vm.getDataFromApi(vm.$route.params.recordid); //let getdata handle loading
|
|
}
|
|
} else {
|
|
window.$gz.form.setFormState({
|
|
vm: vm,
|
|
loading: false
|
|
});
|
|
}
|
|
window.$gz.form.setFormState({
|
|
vm: vm,
|
|
dirty: false,
|
|
valid: true
|
|
});
|
|
|
|
//update which areas are available to user and force generate menu
|
|
updateRights(vm, true);
|
|
} 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 {
|
|
formCustomTemplateKey: FORM_CUSTOM_TEMPLATE_KEY,
|
|
obj: {
|
|
id: 0,
|
|
concurrency: 0,
|
|
serial: 0,
|
|
notes: null,
|
|
wiki: null,
|
|
customFields: "{}",
|
|
tags: [],
|
|
customerId: null,
|
|
projectId: null,
|
|
internalReferenceNumber: null,
|
|
customerReferenceNumber: null,
|
|
customerContactName: null,
|
|
fromQuoteId: null,
|
|
fromPMId: null,
|
|
fromCSRId: null,
|
|
serviceDate: null,
|
|
completeByDate: null,
|
|
invoiceNumber: null,
|
|
customerSignature: null,
|
|
customerSignatureName: null,
|
|
customerSignatureCaptured: null,
|
|
postAddress: null,
|
|
postCity: null,
|
|
postRegion: null,
|
|
postCountry: null,
|
|
postCode: null,
|
|
address: null,
|
|
city: null,
|
|
region: null,
|
|
country: null,
|
|
latitude: null,
|
|
longitude: null,
|
|
items: [],
|
|
states: [],
|
|
isDirty: true,
|
|
isLockedAtServer: false
|
|
},
|
|
formState: {
|
|
ready: false,
|
|
dirty: false,
|
|
valid: true,
|
|
readOnly: false,
|
|
loading: true,
|
|
errorBoxMessage: null,
|
|
appError: null,
|
|
serverError: {}
|
|
},
|
|
rights: window.$gz.role.defaultRightsObject(), //overall workorder rights, supersedes subrights
|
|
ayaType: window.$gz.type.WorkOrder,
|
|
currencyName: window.$gz.locale.getCurrencyName(),
|
|
timeZoneName: window.$gz.locale.getResolvedTimeZoneName(),
|
|
languageName: window.$gz.locale.getResolvedLanguage(),
|
|
hour12: window.$gz.locale.getHour12(),
|
|
deletedGraphItems: { items: [], scheduledUsers: [] }, //todo: other grandchildren
|
|
selectLists: {
|
|
wostatus: [],
|
|
allowedwostatus: []
|
|
},
|
|
subRights: {
|
|
items: {
|
|
visible: true,
|
|
create: true,
|
|
change: true,
|
|
delete: true
|
|
},
|
|
states: {
|
|
visible: true,
|
|
create: true, //no change, only create it's write only
|
|
delete: true
|
|
},
|
|
labors: {
|
|
visible: true,
|
|
create: true,
|
|
change: true,
|
|
delete: true
|
|
},
|
|
parts: {
|
|
visible: true,
|
|
create: true,
|
|
change: true,
|
|
delete: true
|
|
},
|
|
partRequests: {
|
|
visible: true,
|
|
create: true,
|
|
change: true,
|
|
delete: true
|
|
},
|
|
expenses: {
|
|
visible: true,
|
|
create: true,
|
|
change: true,
|
|
delete: true
|
|
},
|
|
loans: {
|
|
visible: true,
|
|
create: true,
|
|
change: true,
|
|
delete: true
|
|
},
|
|
scheduledUsers: {
|
|
visible: true,
|
|
create: true,
|
|
change: true,
|
|
delete: true
|
|
},
|
|
tasks: {
|
|
visible: true,
|
|
create: true,
|
|
change: true,
|
|
delete: true
|
|
},
|
|
travels: {
|
|
visible: true,
|
|
create: true,
|
|
change: true,
|
|
delete: true
|
|
},
|
|
units: {
|
|
visible: true,
|
|
create: true,
|
|
change: true,
|
|
delete: true
|
|
},
|
|
outsideServices: {
|
|
visible: true,
|
|
create: true,
|
|
change: true,
|
|
delete: true
|
|
}
|
|
}
|
|
};
|
|
},
|
|
//WATCHERS
|
|
watch: {
|
|
formState: {
|
|
handler: function(val) {
|
|
//,oldval is available here too if necessary
|
|
if (this.formState.loading) {
|
|
return;
|
|
}
|
|
|
|
//enable / disable save button
|
|
if (val.dirty && val.valid) {
|
|
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: {
|
|
currentState() {
|
|
//return actual status object from top level shell based on current state
|
|
//if state is unknown then it should return a placeholder dummy state showing an error condition or empty I guess
|
|
|
|
if (this.obj.states != null && this.obj.states.length > 0) {
|
|
//find it in the status collection
|
|
//and return here
|
|
const laststate = this.obj.states[this.obj.states.length - 1];
|
|
const found = this.selectLists.wostatus.find(
|
|
z => z.id == laststate.workOrderStatusId
|
|
);
|
|
if (found) {
|
|
return found;
|
|
}
|
|
}
|
|
//default
|
|
return {
|
|
id: 0,
|
|
name: "NOT SET YET",
|
|
active: true,
|
|
color: "#ffffff", //invisible
|
|
completed: false,
|
|
locked: false
|
|
};
|
|
}
|
|
},
|
|
methods: {
|
|
flagGraphItemForDelete: function(item) {
|
|
switch (item.atype) {
|
|
case window.$gz.type.WorkOrderItem:
|
|
this.deletedGraphItems.items.push(item.id);
|
|
break;
|
|
case window.$gz.type.WorkOrderItemScheduledUser:
|
|
this.deletedGraphItems.scheduledUsers.push(item.id);
|
|
break;
|
|
|
|
//todo: other grandchildren
|
|
}
|
|
this.setDirty();
|
|
},
|
|
setDirty: function() {
|
|
this.formState.dirty = true;
|
|
},
|
|
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) {
|
|
window.$gz.form.fieldValueChanged(this, ref);
|
|
}
|
|
},
|
|
async getDataFromApi(recordId) {
|
|
const vm = this;
|
|
window.$gz.form.setFormState({
|
|
vm: vm,
|
|
loading: true
|
|
});
|
|
if (!recordId) {
|
|
throw new Error(FORM_KEY + "::getDataFromApi -> Missing recordID!");
|
|
}
|
|
const url = API_BASE_URL + recordId;
|
|
try {
|
|
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
|
|
|
const 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);
|
|
} else {
|
|
vm.obj = res.data;
|
|
//modify the menu as necessary
|
|
generateMenu(vm);
|
|
//update which areas are available to user
|
|
updateRights(vm);
|
|
|
|
//Update the form status
|
|
window.$gz.form.setFormState({
|
|
vm: vm,
|
|
dirty: false,
|
|
valid: true,
|
|
loading: false
|
|
});
|
|
}
|
|
} catch (error) {
|
|
window.$gz.errorHandler.handleFormError(error, vm);
|
|
} finally {
|
|
window.$gz.form.setFormState({
|
|
vm: vm,
|
|
loading: false
|
|
});
|
|
}
|
|
},
|
|
async submit() {
|
|
const vm = this;
|
|
if (vm.canSave == false) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
window.$gz.form.setFormState({
|
|
vm: vm,
|
|
loading: true
|
|
});
|
|
//const url = API_BASE_URL; // + vm.$route.params.recordid;
|
|
//clear any errors vm might be around from previous submit
|
|
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
|
|
|
//#######################################################
|
|
// ███████╗ █████╗ ██╗ ██╗███████╗
|
|
// ██╔════╝██╔══██╗██║ ██║██╔════╝
|
|
// ███████╗███████║██║ ██║█████╗
|
|
// ╚════██║██╔══██║╚██╗ ██╔╝██╔══╝
|
|
// ███████║██║ ██║ ╚████╔╝ ███████╗
|
|
// ╚══════╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝
|
|
//walk the tree and save dirty
|
|
//items in correct order
|
|
//########################################################
|
|
|
|
const isPost = vm.obj.id == 0;
|
|
|
|
let err = {
|
|
fatal: false, //fatal error, further save is pointless, bail early and report
|
|
error: false, //true if any error regardless if fatal, set by each save / delete
|
|
header: null, //res.error collections exactly as provided by server to be synthesized later
|
|
states: [],
|
|
items: [],
|
|
scheduledUsers: []
|
|
};
|
|
|
|
/*
|
|
TODO: collect return errors but continue to try to save unless fatal error then bail
|
|
report cumulative errors at end as if a single save
|
|
- return object with format: {fatalError:true/false, errors:[collection of errors ]}
|
|
|
|
*/
|
|
|
|
//UNSAVED HEADER MUST BE FIRST
|
|
//(otherwise there's nothing to hang the other things off of)
|
|
let headerSaved = false;
|
|
if (this.obj.concurrency == 0) {
|
|
err = await saveHeader(vm, err);
|
|
headerSaved = true;
|
|
}
|
|
|
|
//LOCKED? State must be saved first then (assuming it unlocks)
|
|
let stateSaved = false;
|
|
if (this.obj.isLockedAtServer) {
|
|
err = await saveState(vm, err);
|
|
if (!err.fatal) {
|
|
stateSaved = true;
|
|
//update which areas are available to user
|
|
//which may have changed due to state being saved (saveState sets the current islocked value)
|
|
updateRights(vm);
|
|
}
|
|
}
|
|
|
|
//############# DELETE GRAPH ###############
|
|
//NOTE: Plan is delete from bottom up
|
|
//SCHEDULED USERS
|
|
if (!err.fatal) {
|
|
err = await deleteScheduledUsers(vm, err);
|
|
}
|
|
|
|
//todo: other grandchildren
|
|
|
|
//WOITEMS
|
|
if (!err.fatal) {
|
|
err = await deleteItems(vm, err);
|
|
}
|
|
|
|
//############ SAVE GRAPH ###############
|
|
//NOTE: Plan is save from top down
|
|
//HEADER
|
|
if (!err.fata && !headerSaved) {
|
|
err = await saveHeader(vm, err);
|
|
}
|
|
|
|
//WOITEMS
|
|
if (!err.fatal) {
|
|
//This saves all bottom level collections as well
|
|
err = await saveItems(vm, err);
|
|
}
|
|
|
|
//### STATE last normally
|
|
//in case it locks or is completed
|
|
if (!err.fatal && !stateSaved) {
|
|
err = await saveState(vm, err);
|
|
if (!err.fatal) {
|
|
updateRights(vm);
|
|
}
|
|
}
|
|
|
|
//## ALL PARTIAL UPDATES COMPLETED
|
|
//todo: walk the err object extract errors and set for display HERE regardless if fatal or not
|
|
//todo: how to proceed?
|
|
//if it's a post and partially successful then what?
|
|
//samesies, if the post partially succeeded that means at least the header succeeded because nothing else would
|
|
//so nothing need be done other than route will show weird (0) in address bar, but object is consistent since
|
|
//the concurrency and id would be set on the header
|
|
//if its' an update easier to just display and bail otu
|
|
|
|
if (err.fatal) {
|
|
//save failed fataly so just set the state and bail out
|
|
window.$gz.form.setFormState({
|
|
vm: vm,
|
|
dirty: true,
|
|
valid: false
|
|
});
|
|
} else {
|
|
if (isPost) {
|
|
//nav to id'd url
|
|
|
|
this.$router.push({
|
|
name: "workorder-edit",
|
|
params: {
|
|
recordid: vm.obj.id,
|
|
obj: vm.obj // Pass data object to new form
|
|
}
|
|
});
|
|
} else {
|
|
window.$gz.form.setFormState({
|
|
vm: vm,
|
|
dirty: false,
|
|
valid: true
|
|
});
|
|
}
|
|
}
|
|
} catch (ex) {
|
|
window.$gz.errorHandler.handleFormError(ex, vm);
|
|
} finally {
|
|
window.$gz.form.setFormState({
|
|
vm: vm,
|
|
loading: false
|
|
});
|
|
}
|
|
},
|
|
async remove() {
|
|
let vm = this;
|
|
try {
|
|
let dialogResult = await window.$gz.dialog.confirmDelete();
|
|
if (dialogResult != true) {
|
|
return;
|
|
}
|
|
|
|
//do the delete
|
|
window.$gz.form.setFormState({
|
|
vm: vm,
|
|
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 (error) {
|
|
//Update the form status
|
|
window.$gz.form.setFormState({
|
|
vm: vm,
|
|
loading: false
|
|
});
|
|
window.$gz.errorHandler.handleFormError(error, vm);
|
|
}
|
|
},
|
|
async duplicate() {
|
|
let vm = this;
|
|
if (!vm.canDuplicate || vm.$route.params.recordid == 0) {
|
|
return;
|
|
}
|
|
window.$gz.form.setFormState({
|
|
vm: vm,
|
|
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: "workorder-edit",
|
|
params: {
|
|
recordid: res.data.id,
|
|
obj: res.data // Pass data object to new form
|
|
}
|
|
});
|
|
}
|
|
} catch (ex) {
|
|
window.$gz.errorHandler.handleFormError(ex, vm);
|
|
} finally {
|
|
window.$gz.form.setFormState({
|
|
vm: vm,
|
|
loading: false
|
|
});
|
|
}
|
|
}
|
|
//end methods
|
|
}
|
|
};
|
|
|
|
//########################################## SAVE METHODS ##############################################
|
|
|
|
/////////////////////////////
|
|
// HEADER
|
|
//
|
|
async function saveHeader(vm, err) {
|
|
if (!vm.obj.isDirty) {
|
|
return err;
|
|
}
|
|
const isPost = vm.obj.id == 0;
|
|
|
|
//the purpose of this is to remove the child collections so only the header itself is submitted
|
|
//this was the cleanest way I could find to accomplish this
|
|
//https://stackoverflow.com/a/58206483/8939
|
|
const { items: removedKey1, states: removedKey2, ...headerOnly } = vm.obj;
|
|
|
|
let res = await window.$gz.api.upsert(`${API_BASE_URL}`, headerOnly);
|
|
if (res.error) {
|
|
if (isPost) {
|
|
err.fatal = true;
|
|
}
|
|
err.header = res.error;
|
|
err.error = true;
|
|
return err;
|
|
} else {
|
|
//update any server changed fields
|
|
vm.obj.concurrency = res.data.concurrency;
|
|
vm.obj.isDirty = false;
|
|
if (isPost) {
|
|
vm.obj.id = res.data.id;
|
|
vm.obj.serial = res.data.serial;
|
|
//walk all unsaved direct children and set the workorder id so they can save
|
|
vm.obj.states.forEach(z => (z.workOrderId = vm.obj.id));
|
|
vm.obj.items.forEach(z => (z.workOrderId = vm.obj.id));
|
|
}
|
|
return err;
|
|
}
|
|
}
|
|
|
|
/////////////////////////////
|
|
// STATES
|
|
//
|
|
async function saveState(vm, err) {
|
|
//CHANGED?
|
|
let totalItems = vm.obj.states.length;
|
|
if (totalItems == 0) {
|
|
return err;
|
|
}
|
|
for (let i = 0; i < totalItems; i++) {
|
|
let o = vm.obj.states[i];
|
|
if (o.concurrency == null) {
|
|
//it's new so save it
|
|
let res = await window.$gz.api.upsert(`${API_BASE_URL}states`, o);
|
|
if (res.error) {
|
|
err.states.push(res.error);
|
|
err.error = true;
|
|
} else {
|
|
vm.obj.states[i] = res.data;
|
|
|
|
//set locked status of entire wo now
|
|
vm.obj.isLockedAtServer = vm.currentState.locked;
|
|
}
|
|
}
|
|
}
|
|
return err;
|
|
}
|
|
|
|
/////////////////////////////
|
|
// ITEMS
|
|
//
|
|
async function deleteItems(vm, err) {
|
|
if (vm.deletedGraphItems.items.length == 0) {
|
|
return err;
|
|
}
|
|
//walk the array backwards as items may or may not be spliced out
|
|
for (var i = vm.deletedGraphItems.items.length - 1; i >= 0; i--) {
|
|
let res = await window.$gz.api.remove(
|
|
`${API_BASE_URL}items/${vm.deletedGraphItems.items[0]}`
|
|
);
|
|
if (res.error) {
|
|
err.items.push(res.error);
|
|
err.error = true;
|
|
} else {
|
|
vm.deletedGraphItems.items.splice(i, 1);
|
|
}
|
|
return err;
|
|
}
|
|
}
|
|
async function saveItems(vm) {
|
|
let totalItems = vm.obj.items.length;
|
|
if (totalItems == 0) {
|
|
return true;
|
|
}
|
|
|
|
for (let i = 0; i < totalItems; i++) {
|
|
//get copy of item without child collections for independant submit
|
|
const {
|
|
expenses: removedKey1,
|
|
labors: removedKey2,
|
|
loans: removedKey3,
|
|
parts: removedKey4,
|
|
partRequests: removedKey5,
|
|
scheduledUsers: removedKey6,
|
|
tasks: removedKey7,
|
|
travels: removedKey8,
|
|
units: removedKey9,
|
|
outsideServices: removedKey10,
|
|
...o
|
|
} = vm.obj.items[i];
|
|
if (o.isDirty) {
|
|
const isPost = o.id == 0;
|
|
let res = await window.$gz.api.upsert(`${API_BASE_URL}items`, o);
|
|
if (res.error) {
|
|
err.items.push(res.error);
|
|
err.error = true;
|
|
if (isPost) {
|
|
//a post error precludes further operations on this item below
|
|
//however, an update error doesn't necessarily because it's still a existing workorder item
|
|
//so it's children can probably be updated and we want that
|
|
continue;
|
|
}
|
|
} else {
|
|
//no error so update any server changed fields
|
|
//put fields
|
|
vm.obj.items[i].concurrency = res.data.concurrency;
|
|
vm.obj.items[i].isDirty = false;
|
|
|
|
//fields to update if post
|
|
if (isPost) {
|
|
vm.obj.items[i].id = res.data.id;
|
|
vm.obj.items[i].workorderId = res.data.workorderId;
|
|
//walk all unsaved children and set the workorder id so they can save
|
|
vm.obj.items[i].scheduledUsers.forEach(
|
|
z => (z.workorderItemId = vm.obj.items[i].id)
|
|
);
|
|
//todo: other grandchildren
|
|
}
|
|
}
|
|
} else {
|
|
// console.log("Item not dirty skipping save of ", i);
|
|
}
|
|
//------
|
|
//save grandchildren
|
|
err = await saveScheduledUsers(vm, i.err);
|
|
if (err.fatal) {
|
|
return err;
|
|
}
|
|
//todo: other grandchildren
|
|
//------
|
|
}
|
|
return err; //made it through
|
|
}
|
|
//####################################################################################
|
|
//## GRANDCHILDREN
|
|
|
|
/////////////////////////////
|
|
// SCHEDULED USERS
|
|
//
|
|
async function deleteScheduledUsers(vm, err) {
|
|
//walk the array backwards as items may or may not be spliced out
|
|
for (var i = vm.deletedGraphItems.scheduledUsers.length - 1; i >= 0; i--) {
|
|
// while (vm.deletedGraphItems.scheduledUsers.length) {
|
|
let res = await window.$gz.api.remove(
|
|
`${API_BASE_URL}items/scheduledusers/${vm.deletedGraphItems.scheduledUsers[0]}`
|
|
);
|
|
if (res.error) {
|
|
err.items.push(res.error);
|
|
err.error = true;
|
|
} else {
|
|
vm.deletedGraphItems.scheduledUsers.splice(i, 1);
|
|
}
|
|
}
|
|
return err;
|
|
}
|
|
|
|
async function saveScheduledUsers(vm, woitemindex) {
|
|
let totalItems = vm.obj.items[woitemindex].scheduledUsers.length;
|
|
if (totalItems == 0) {
|
|
return true;
|
|
}
|
|
for (let i = 0; i < totalItems; i++) {
|
|
let o = vm.obj.items[woitemindex].scheduledUsers[i];
|
|
if (o.isDirty) {
|
|
const isPost = o.id == 0;
|
|
let res = await window.$gz.api.upsert(
|
|
`${API_BASE_URL}items/scheduledusers`,
|
|
o
|
|
);
|
|
if (res.error) {
|
|
displayResError(vm, res);
|
|
return false;
|
|
} else {
|
|
//update any server changed fields
|
|
//always updated:
|
|
o.concurrency = res.data.concurrency;
|
|
o.isDirty = false;
|
|
|
|
//new records only...
|
|
if (isPost) {
|
|
o.id = res.data.id;
|
|
o.workOrderItemId = res.data.workOrderItemId;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true; //made it
|
|
}
|
|
|
|
//todo: other grandchildren
|
|
|
|
// /////////////////////////////
|
|
// // Error display
|
|
// //
|
|
// function displayResError(vm, res) {
|
|
// console.log("DISPLAY RES ERROR: ", res);
|
|
// vm.formState.serverError = res.error;
|
|
// window.$gz.form.setErrorBoxErrors(vm);
|
|
// }
|
|
|
|
//######################################### UTILITY METHODS ###########################################
|
|
/////////////////////////////
|
|
//
|
|
//
|
|
function updateRights(vm, forceGenerateMenu) {
|
|
//determine rights to each which sections are hidden due to form customized out or rights / roles
|
|
const readOnlyBefore = vm.formState.readOnly;
|
|
if (vm.obj.isLockedAtServer) {
|
|
//locked is always read only (with state exception for sufficient roles)
|
|
vm.formState.readOnly = true;
|
|
} else {
|
|
//state may have changed so update readOnly
|
|
vm.formState.readOnly = !vm.rights.change;
|
|
}
|
|
|
|
if (readOnlyBefore != vm.formState.readOnly || forceGenerateMenu === true) {
|
|
generateMenu(vm);
|
|
}
|
|
|
|
/*
|
|
*/
|
|
}
|
|
|
|
/////////////////////////////
|
|
//
|
|
//
|
|
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: "workorder-edit",
|
|
params: { recordid: 0, new: true }
|
|
});
|
|
break;
|
|
case "duplicate":
|
|
m.vm.duplicate();
|
|
break;
|
|
case "report":
|
|
if (m.id != null) {
|
|
//last report selected is in m.id
|
|
m.vm.$router.push({
|
|
name: "ay-report",
|
|
params: { recordid: m.id, ayatype: window.$gz.type.WorkOrder }
|
|
});
|
|
} else {
|
|
//general report selector chosen
|
|
|
|
let res = await m.vm.$refs.reportSelector.open({
|
|
AType: window.$gz.type.WorkOrder,
|
|
selectedRowIds: [m.vm.obj.id]
|
|
});
|
|
|
|
//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.WorkOrder }
|
|
});
|
|
}
|
|
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: "$ayiTools",
|
|
title: "WorkOrder",
|
|
helpUrl: "svc-workorder",
|
|
formData: {
|
|
ayaType: window.$gz.type.WorkOrder,
|
|
recordId: vm.$route.params.recordid,
|
|
formCustomTemplateKey: FORM_CUSTOM_TEMPLATE_KEY,
|
|
recordName: vm.obj.serial
|
|
},
|
|
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
|
|
});
|
|
}
|
|
|
|
//REPORTS
|
|
//Report not Print, print is a further option
|
|
menuOptions.menuItems.push({
|
|
title: "Report",
|
|
icon: "$ayiFileAlt",
|
|
key: FORM_KEY + ":report",
|
|
vm: vm
|
|
});
|
|
|
|
//get last report selected
|
|
let lastReport = window.$gz.form.getLastReport(FORM_KEY);
|
|
if (lastReport != null) {
|
|
menuOptions.menuItems.push({
|
|
title: lastReport.name,
|
|
icon: "$ayiFileAlt",
|
|
key: FORM_KEY + ":report:" + lastReport.id,
|
|
vm: vm
|
|
});
|
|
}
|
|
|
|
if (vm.rights.change) {
|
|
menuOptions.menuItems.push({
|
|
title: "New",
|
|
icon: "$ayiPlus",
|
|
key: FORM_KEY + ":new",
|
|
vm: vm
|
|
});
|
|
}
|
|
|
|
if (vm.rights.change) {
|
|
menuOptions.menuItems.push({
|
|
title: "Duplicate",
|
|
icon: "$ayiClone",
|
|
key: FORM_KEY + ":duplicate",
|
|
vm: vm
|
|
});
|
|
}
|
|
|
|
//--- /show all ---
|
|
|
|
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);
|
|
await populateSelectionLists(vm);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////
|
|
//
|
|
// Ensures UI translated text is available
|
|
//
|
|
async function fetchTranslatedText(vm) {
|
|
await window.$gz.translation.cacheTranslations([
|
|
"WorkOrder",
|
|
"Customer",
|
|
"WorkOrderSerialNumber",
|
|
"WorkOrderSummary",
|
|
"WorkOrderStatus",
|
|
"WorkOrderCustom1",
|
|
"WorkOrderCustom2",
|
|
"WorkOrderCustom3",
|
|
"WorkOrderCustom4",
|
|
"WorkOrderCustom5",
|
|
"WorkOrderCustom6",
|
|
"WorkOrderCustom7",
|
|
"WorkOrderCustom8",
|
|
"WorkOrderCustom9",
|
|
"WorkOrderCustom10",
|
|
"WorkOrderCustom11",
|
|
"WorkOrderCustom12",
|
|
"WorkOrderCustom13",
|
|
"WorkOrderCustom14",
|
|
"WorkOrderCustom15",
|
|
"WorkOrderCustom16",
|
|
"WorkOrderItemList",
|
|
"WorkOrderItemTechNotes",
|
|
"WorkOrderItemScheduledUserList",
|
|
"WorkOrderItemSummary",
|
|
"WorkOrderItemWorkOrderStatusID",
|
|
"WorkOrderItemRequestDate",
|
|
"WorkOrderItemPriorityID",
|
|
"WorkOrderItemWarrantyService",
|
|
"WorkOrderItemScheduledUserStartDate",
|
|
"WorkOrderItemScheduledUserStopDate",
|
|
"WorkOrderItemScheduledUserEstimatedQuantity",
|
|
"WorkOrderItemScheduledUserUserID",
|
|
"WorkOrderItemScheduledUserServiceRateID"
|
|
]);
|
|
}
|
|
|
|
//////////////////////
|
|
//
|
|
//
|
|
async function populateSelectionLists(vm) {
|
|
let res = await window.$gz.api.get("work-order-status/list");
|
|
if (res.error) {
|
|
vm.formState.serverError = res.error;
|
|
window.$gz.form.setErrorBoxErrors(vm);
|
|
//window.$gz.errorHandler.handleFormError(res.error, vm);
|
|
} else {
|
|
vm.selectLists.wostatus = res.data.all;
|
|
vm.selectLists.allowedwostatus = res.data.allowed;
|
|
}
|
|
|
|
//---------
|
|
}
|
|
</script>
|