Files
raven-client/ayanova/src/views/svc-workorder.vue
2022-11-03 18:33:59 +00:00

2473 lines
71 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>
<gz-alert :alert-message="obj.alertViz" pop-alert></gz-alert>
<gz-alert :alert-message="alertTechNotes"></gz-alert>
<!-- SERVERERROR:{{ formState.serverError }} -->
<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"
:goto="this.goto"
data-cy="woItems"
class="mt-16"
@change="setDirty()"
/>
</v-form>
</div>
<v-overlay
:value="!formState.ready || formState.loading"
data-cy="busyoverlay"
>
<v-progress-circular indeterminate :size="64" />
</v-overlay>
<!-- ################################################################################-->
<!-- ########################## DUPLICATE DIALOG ###############################-->
<!-- ################################################################################-->
<template>
<v-row dense justify="center">
<v-dialog v-model="duplicateDlg" persistent max-width="600px">
<v-card>
<v-card-title>{{ duplicateDlgTitle }}</v-card-title>
<v-card-text>
<v-checkbox
v-model="genCopyWiki"
dense
:label="$ay.t('CopyWiki')"
></v-checkbox>
<v-checkbox
v-model="genCopyAttachments"
dense
:label="$ay.t('CopyAttachments')"
></v-checkbox>
</v-card-text>
<v-card-actions>
<v-btn text color="primary" @click="duplicateDlg = false">{{
$ay.t("Cancel")
}}</v-btn>
<v-spacer></v-spacer>
<v-btn color="primary" text @click="duplicateHandler()">{{
$ay.t("OK")
}}</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-row>
</template>
</div>
</template>
<script>
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";
export default {
components: {
GzWoHeader,
GzWoItems
},
data() {
return {
formCustomTemplateKey: FORM_CUSTOM_TEMPLATE_KEY,
obj: {
id: 0,
concurrency: 0,
serial: 0,
notes: null,
wiki: null,
customFields: "{}",
tags: [],
customerId: 0, //part of rule breaking, flip to null on new triggers broken rule
projectId: null,
contractId: null,
internalReferenceNumber: null,
customerReferenceNumber: null,
customerContactName: null,
fromQuoteId: null,
fromPMId: null,
serviceDate: window.$gz.locale.nowUTC8601String(),
completeByDate: null,
durationToCompleted: "00:00:00",
invoiceNumber: null,
onsite: true,
customerSignature: null,
customerSignatureName: null,
customerSignatureCaptured: null,
techSignature: null,
techSignatureName: null,
techSignatureCaptured: 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,
alertViz: null
},
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.WorkOrder,
currencyName: window.$gz.locale.getCurrencyName(),
timeZoneName: window.$gz.locale.getResolvedTimeZoneName(),
languageName: window.$gz.locale.getResolvedLanguage(),
hour12: window.$gz.locale.getHour12(),
selectLists: {
wostatus: [],
allowedwostatus: [],
woItemPriorities: [],
woItemStatus: [],
woItemTaskCompletionTypes: [],
loanUnitRateUnits: []
},
maxTableNotesLength: 50, //value to cut off notes in tables
saveResult: {
fatal: false, //fatal error, further save is pointless, bail early and report
errors: null //contains error objects from save
},
lastGetContractId: -1, //note: -1 so that a new record updates
lastGetCustomerId: -1,
goto: null, //{type:ayatype,id:wodescendant id} picked up by woitem when set non-null to trigger navigate to the item in question
duplicateDlg: false,
duplicateDlgTitle: null,
duplicateTo: null, //"pm","wo" or "quote"
genCopyWiki: false,
genCopyAttachments: false
};
},
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: "-",
active: true,
color: "#ffffffff", //invisible
completed: false,
locked: false
};
},
useInventory() {
return window.$gz.store.state.globalSettings.useInventory;
},
alertTechNotes() {
return this.obj.customerTechNotesViz
? `${this.$ay.t("CustomerTechNotes")}\n${this.obj.customerTechNotesViz}`
: null;
}
},
watch: {
formState: {
handler: function(val) {
if (this.formState.loading) {
return;
}
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");
}
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
}
},
async created() {
const vm = this;
try {
await initForm(vm);
vm.rights = window.$gz.role.getRights(window.$gz.type.WorkOrder);
window.$gz.eventBus.$on("menu-click", clickHandler);
let setDirty = false;
//id 0 means create or duplicate to new
if (vm.$route.params.recordid != 0) {
//open existing path
//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);
//check for copy wo item on route params
const wi = this.$route.params.copyItem;
if (wi) {
this.washWorkOrderItem(wi);
wi.workOrderId = vm.obj.id;
wi.sequence = vm.obj.items.length + 1;
vm.obj.items.push(wi);
setDirty = true;
}
}
} else {
//new path
if (this.$route.params.obj) {
//DUPLICATE OR GEN FROM CSR, QUOTE OR PM
//A whole work order basically is presented here
this.obj = this.$route.params.obj;
this.obj.concurrency = undefined;
this.obj.id = 0;
this.obj.serial = 0;
this.obj.isDirty = true;
//set the service date if not set by the caller
if (this.obj.serviceDate == null) {
this.obj.serviceDate = window.$gz.locale.nowUTC8601String();
}
vm.obj.items.forEach(z => {
z.workOrderId = 0;
this.washWorkOrderItem(z);
});
setDirty = true;
} else {
//sometimes we're here with some data to fill in (e.g. schedule creates new wo itemscheduled user, if so there will be a add property on the params)
if (this.$route.params.add) {
const n = this.$route.params.add;
switch (n.type) {
case this.$ay.ayt().WorkOrderItemScheduledUser:
this.obj.items.push({
id: 0,
concurrency: 0,
notes: "-", //todo: default here at some point or just leave this TTM
wiki: null,
customFields: "{}",
tags: [],
workOrderId: null,
techNotes: null,
workOrderItemStatusId: null,
workOrderItemPriorityId: null,
requestDate: null,
warrantyService: false,
sequence: 1, //indexes are zero based but sequences are visible to user so 1 based
isDirty: true,
expenses: [],
labors: [],
loans: [],
parts: [],
partRequests: [],
scheduledUsers: [
{
id: 0,
concurrency: 0,
userId: n.userId,
estimatedQuantity: window.$gz.locale.diffHoursFromUTC8601String(
n.start,
n.end
),
startDate: n.start,
stopDate: n.end,
serviceRateId: null,
isDirty: true,
workOrderItemId: 0,
uid: Date.now(),
userViz: n.name,
serviceRateViz: null
}
],
tasks: [],
travels: [],
units: [],
outsideServices: [],
uid: Date.now() //used for error tracking / display
});
this.obj.serviceDate = n.start;
break;
default:
if (window.$gz.dev) {
throw new Error(
`svc-workorder:created - new from Add - type '${n.type}' not recognized`
);
}
console.error(
`svc-workorder:created - new from Add - type '${n.type}' not recognized`
);
break;
}
}
//NEW
this.formState.loading = false;
this.formState.ready = true;
//trigger rule breaking / validation
this.$nextTick(() => {
this.obj.customerId = null;
this.fieldValueChanged(`customerId`);
});
}
}
window.$gz.form.setFormState({
vm: vm,
loading: false,
dirty: setDirty,
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;
//navigate somewhere??
if (this.$route.params.gotype != null) {
//find out which woitem and then find out which type to set and then set them as the active item somehow
const gotype = Number(this.$route.params.gotype);
const goid = Number(this.$route.params.goid);
if (gotype != window.$gz.type.WorkOrder) {
this.$nextTick(() => {
//not workorder? Then must be a descendant so let's gooooooooooo!
this.goto = { type: gotype, id: goid };
});
}
}
}
},
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);
},
methods: {
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) {
if (res.error.code == "2010") {
window.$gz.form.handleObjectNotFound(vm);
}
vm.formState.serverError = res.error;
window.$gz.form.setErrorBoxErrors(vm);
} else {
//assign opening values
res.data.isDirty = false;
res.data.items.forEach((z, index) => {
z.uid = index;
z.isDirty = false;
z.expenses.forEach((x, index) => {
x.uid = index;
x.isDirty = false;
});
z.labors.forEach((x, index) => {
x.uid = index;
x.isDirty = false;
});
z.loans.forEach((x, index) => {
x.uid = index;
x.isDirty = false;
});
z.parts.forEach((x, index) => {
x.uid = index;
x.isDirty = false;
});
z.partRequests.forEach((x, index) => {
x.uid = index;
x.isDirty = false;
});
z.scheduledUsers.forEach((x, index) => {
x.uid = index;
x.isDirty = false;
});
z.tasks.forEach((x, index) => {
x.uid = index;
x.isDirty = false;
});
z.travels.forEach((x, index) => {
x.uid = index;
x.isDirty = false;
});
z.units.forEach((x, index) => {
x.uid = index;
x.isDirty = false;
x.warrantyViz = null; //locally added field for reactivity, set on demand in UI not by server
});
z.outsideServices.forEach((x, index) => {
x.uid = index;
x.isDirty = false;
});
});
//insert into vue data
vm.obj = res.data;
vm.lastGetContractId = vm.obj.contractId; //preserve for triggering full update if something changes it later
vm.lastGetCustomerId = vm.obj.customerId; //preserve for triggering full update if something changes it later
generateMenu(vm);
//update which areas are available to user
updateRights(vm);
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
});
window.$gz.form.deleteAllErrorBoxErrors(vm);
//#######################################################
// ███████╗ █████╗ ██╗ ██╗███████╗
// ██╔════╝██╔══██╗██║ ██║██╔════╝
// ███████╗███████║██║ ██║█████╗
// ╚════██║██╔══██║╚██╗ ██╔╝██╔══╝
// ███████║██║ ██║ ╚████╔╝ ███████╗
// ╚══════╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝
//########################################################
const isPost = vm.obj.id == 0;
//some ops require a full refresh like ones that trigger contract changes
let forceFullRefresh = false;
//reset error object
this.saveResult.fatal = false;
this.saveResult.errors = null;
//UNSAVED HEADER MUST BE FIRST
//(otherwise there's nothing to hang the other things off of)
let headerSaved = false;
if (this.obj.concurrency == 0) {
await saveHeader(vm);
headerSaved = true;
}
//LOCKED? State must be saved first then (assuming it unlocks)
let stateSaved = false;
if (this.obj.isLockedAtServer) {
await saveState(vm);
if (!this.saveResult.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);
}
}
//HEADER
if (!this.saveResult.fatal && !headerSaved) {
await saveHeader(vm);
}
//WOITEMS
if (!this.saveResult.fatal) {
//first sort items into sequence order so that the errors line up with the display
this.obj.items.sort((a, b) => a.sequence - b.sequence);
//This saves all bottom level collections as well
await saveItems(vm);
}
//### STATE last normally
//in case it locks or is completed
if (!this.saveResult.fatal && !stateSaved) {
await saveState(vm);
if (!this.saveResult.fatal) {
updateRights(vm);
}
}
//## ALL PARTIAL UPDATES COMPLETED
//handle errors
if (this.saveResult.errors != null) {
//# FAIL ROUTE
const processedErrors = formErrorFromSaveResult(this);
vm.formState.serverError = processedErrors; //this is the bit that triggers field and row errors to display as they pickup from this setting
window.$gz.form.setErrorBoxErrors(vm); //set generalerror errors in error box at top, not related to form field errors which happen alternatively based on formState.serverError which is confusing
} else {
//# SUCCESS ROUTE
if (isPost) {
//nav to id'd url
//note that an isPost will never be here if there is a fatal error so this is safe to do
this.$router.replace({
name: "workorder-edit",
params: {
recordid: vm.obj.id
// Do NOT Pass data object to new form as normal because for a workorder, it's not a full and complete record at this end
}
});
} else {
//check if full refresh is necessary
if (
vm.obj.contractId != vm.lastGetContractId ||
vm.obj.customerId != vm.lastGetCustomerId
) {
//there may be others which is why I'm doing it this way with the extra variable
forceFullRefresh = true;
}
if (forceFullRefresh) {
await vm.getDataFromApi(vm.$route.params.recordid);
} 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
// });
this.$nextTick(() => {
window.$gz.form.setFormState({
vm: vm,
loading: false
});
});
}
},
async remove() {
try {
const dialogResult = await window.$gz.dialog.confirmDelete();
if (dialogResult != true) {
return;
}
window.$gz.form.setFormState({
vm: this,
loading: true
});
if (this.$route.params.recordid == 0) {
JUST_DELETED = true;
this.$router.go(-1);
} else {
window.$gz.form.deleteAllErrorBoxErrors(this);
const res = await window.$gz.api.remove(
API_BASE_URL + this.$route.params.recordid
);
if (res.error) {
this.formState.serverError = res.error;
window.$gz.form.setErrorBoxErrors(this);
} else {
JUST_DELETED = true;
this.$router.go(-1);
}
}
} catch (error) {
window.$gz.errorHandler.handleFormError(error, this);
} finally {
window.$gz.form.setFormState({
vm: this,
loading: false
});
}
},
duplicateHandler() {
switch (this.duplicateTo) {
case "pm":
this.generatePM();
break;
case "wo":
this.duplicate();
break;
case "quote":
this.generateQuote();
break;
}
},
duplicate() {
const cp = JSON.parse(JSON.stringify(this.obj));
if (this.genCopyAttachments) {
//this property set will trigger server to copy attachments
cp.genCopyAttachmentsFrom = {
sAType: window.$gz.type.WorkOrder,
sId: this.obj.id
};
}
if (!this.genCopyWiki) {
cp.wiki = null; //already copied, need to remove it instead
}
this.$router.push({
name: "workorder-edit",
params: {
recordid: 0,
obj: cp
}
});
},
generateQuote() {
const cp = JSON.parse(JSON.stringify(this.obj));
if (this.genCopyAttachments) {
//this property set will trigger server to copy attachments
cp.genCopyAttachmentsFrom = {
sAType: window.$gz.type.WorkOrder,
sId: this.obj.id
};
}
if (!this.genCopyWiki) {
cp.wiki = null; //already copied, need to remove it instead
}
cp.states = [];
cp.lastStatusId = undefined;
cp.items.forEach(x => {
x.partRequests = [];
});
cp.id = 0;
//set quote specific fields
cp.preparedById = window.$gz.store.state.userId;
cp.introduction = null;
cp.requested = window.$gz.locale.nowUTC8601String();
cp.validUntil = null;
cp.submitted = null;
cp.approved = null;
this.$router.push({
name: "quote-edit",
params: {
recordid: 0,
obj: cp
}
});
},
generatePM() {
const cp = JSON.parse(JSON.stringify(this.obj));
if (this.genCopyAttachments) {
//this property set will trigger server to copy attachments
cp.genCopyAttachmentsFrom = {
sAType: window.$gz.type.WorkOrder,
sId: this.obj.id
};
}
if (!this.genCopyWiki) {
cp.wiki = null; //already copied, need to remove it instead
}
cp.states = [];
cp.items.forEach(x => {
x.partRequests = [];
});
cp.id = 0;
//PM Defaults copied from PM data object
cp.stopGeneratingDate = null;
cp.excludeDaysOfWeek = 96;
cp.active = true;
cp.nextServiceDate = this.obj.serviceDate;
cp.repeatUnit = 7;
cp.generateBeforeUnit = 4;
cp.repeatInterval = 1;
cp.generateBeforeInterval = 14;
cp.generateDate = null;
this.$router.push({
name: "pm-edit",
params: {
recordid: 0,
obj: cp
}
});
},
/////////////////////////////////////////////////////////
// Clean woitem and children so it's
// savable as a new record
// (used by duplicate and copy wo item functions
// also called from work-order-items.vue copy woitem when
// self target)
//
washWorkOrderItem(wi) {
if (wi) {
wi.id = 0;
wi.concurrency = 0;
wi.uid = Date.now();
wi.isDirty = true;
if (!wi.keepCSR) {
//workaround so when sent from CSR it doesn't erase it
wi.fromCSRId = null;
}
if (wi.partRequests) {
wi.partRequests.splice(0);
}
wi.expenses.forEach(x => {
x.id = 0;
x.workOrderItemId = 0;
x.concurrency = undefined;
x.isDirty = true;
});
wi.labors.forEach(x => {
x.id = 0;
x.workOrderItemId = 0;
x.concurrency = undefined;
x.isDirty = true;
});
wi.loans.forEach(x => {
x.id = 0;
x.workOrderItemId = 0;
x.concurrency = undefined;
x.isDirty = true;
x.cost = 0;
x.listPrice = 0;
});
wi.parts.forEach(x => {
x.id = 0;
x.workOrderItemId = 0;
x.concurrency = undefined;
x.isDirty = true;
x.cost = 0;
x.listPrice = 0;
});
wi.scheduledUsers.forEach(x => {
x.id = 0;
x.workOrderItemId = 0;
x.concurrency = undefined;
x.isDirty = true;
});
wi.tasks.forEach(x => {
x.id = 0;
x.workOrderItemId = 0;
x.concurrency = undefined;
x.isDirty = true;
});
wi.travels.forEach(x => {
x.id = 0;
x.workOrderItemId = 0;
x.concurrency = undefined;
x.isDirty = true;
});
wi.units.forEach(x => {
x.id = 0;
x.workOrderItemId = 0;
x.concurrency = undefined;
x.isDirty = true;
});
wi.outsideServices.forEach(x => {
x.id = 0;
x.workOrderItemId = 0;
x.concurrency = undefined;
x.isDirty = true;
});
}
}
}
};
//########################################## SAVE METHODS ##############################################
/////////////////////////////
// HEADER
//
async function saveHeader(vm) {
if (!vm.obj.isDirty) {
return;
}
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
/* eslint-disable no-unused-vars */
const { items: removedKey1, states: removedKey2, ...headerOnly } = vm.obj;
/* eslint-enable no-unused-vars */
//remove *Viz keys so they don't generate traffic
headerOnly.alertViz = undefined;
headerOnly.projectViz = undefined;
headerOnly.contractViz = undefined;
headerOnly.customerViz = undefined;
const res = await window.$gz.api.upsert(`${API_BASE_URL}`, headerOnly);
if (res.error) {
handleSaveError(vm, { fatal: true, error: res.error });
} else {
//update any server changed fields
vm.obj.concurrency = res.data.concurrency;
vm.obj.isDirty = false;
vm.obj.customerId = res.data.customerId;
vm.obj.contractId = res.data.contractId; //may or may not have changed at server, this will ensure entire ui gets updated if it has as all prices may have changed and other stuff
//repopulate *viz fields from return value
vm.obj.alertViz = res.data.alertViz;
vm.obj.projectViz = res.data.projectViz;
vm.obj.contractViz = res.data.contractViz;
vm.obj.customerViz = res.data.customerViz;
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));
}
}
}
/////////////////////////////
// STATES
//
async function saveState(vm) {
//CHANGED?
const totalItems = vm.obj.states.length;
if (totalItems == 0) {
return;
}
for (let i = 0; i < totalItems; i++) {
const o = vm.obj.states[i];
if (o.concurrency == null) {
//it's new so save it
const res = await window.$gz.api.upsert(`${API_BASE_URL}states`, o);
if (res.error) {
handleSaveError(vm, { error: res.error });
} else {
vm.obj.states[i] = res.data;
//changing status updates the laststatus in the wo header so need to update concurrency here as well
vm.obj.concurrency = res.data.newWOConcurrency;
//set locked status of entire wo now
vm.obj.isLockedAtServer = vm.currentState.locked;
}
}
}
}
/////////////////////////////
// ITEMS
//
async function deleteItems(vm) {
//walk the array backwards as items may or may not be spliced out
for (var i = vm.obj.items.length - 1; i >= 0; i--) {
const d = vm.obj.items[i];
if (!d.deleted) {
continue;
}
const res = await window.$gz.api.remove(`${API_BASE_URL}items/${d.id}`);
if (res.error) {
handleSaveError(vm, { error: res.error, itemUid: d.uid });
} else {
vm.obj.items.splice(i, 1);
}
}
return;
}
async function saveItems(vm) {
//DELETE FLAGGED WOITEMS FIRST
await deleteItems(vm);
if (vm.saveResult.fatal) {
return;
}
//SAVE WOITEMS
for (let i = 0; i < vm.obj.items.length; i++) {
if (vm.obj.items[i].isDirty) {
//get copy of item without child collections for independant submit
/* eslint-disable no-unused-vars */
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];
/* eslint-enable no-unused-vars */
const isPost = o.id == 0;
const res = await window.$gz.api.upsert(`${API_BASE_URL}items`, o);
if (res.error) {
handleSaveError(vm, { error: res.error, itemUid: o.uid });
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 item id so they can save
vm.obj.items[i].units.forEach(
z => (z.workOrderItemId = vm.obj.items[i].id)
);
vm.obj.items[i].scheduledUsers.forEach(
z => (z.workOrderItemId = vm.obj.items[i].id)
);
vm.obj.items[i].tasks.forEach(
z => (z.workOrderItemId = vm.obj.items[i].id)
);
vm.obj.items[i].parts.forEach(
z => (z.workOrderItemId = vm.obj.items[i].id)
);
vm.obj.items[i].partRequests.forEach(
z => (z.workOrderItemId = vm.obj.items[i].id)
);
vm.obj.items[i].labors.forEach(
z => (z.workOrderItemId = vm.obj.items[i].id)
);
vm.obj.items[i].travels.forEach(
z => (z.workOrderItemId = vm.obj.items[i].id)
);
vm.obj.items[i].expenses.forEach(
z => (z.workOrderItemId = vm.obj.items[i].id)
);
vm.obj.items[i].loans.forEach(
z => (z.workOrderItemId = vm.obj.items[i].id)
);
vm.obj.items[i].outsideServices.forEach(
z => (z.workOrderItemId = vm.obj.items[i].id)
);
}
}
}
//------
//save grandchildren
if (!vm.saveResult.fatal) {
await saveUnits(vm, i);
}
if (!vm.saveResult.fatal) {
await saveScheduledUsers(vm, i);
}
if (!vm.saveResult.fatal) {
await saveTasks(vm, i);
}
if (!vm.saveResult.fatal) {
await saveParts(vm, i);
}
if (!vm.saveResult.fatal) {
await savePartRequests(vm, i);
}
if (!vm.saveResult.fatal) {
await saveLabors(vm, i);
}
if (!vm.saveResult.fatal) {
await saveTravels(vm, i);
}
if (!vm.saveResult.fatal) {
await saveExpenses(vm, i);
}
if (!vm.saveResult.fatal) {
await saveLoans(vm, i);
}
if (!vm.saveResult.fatal) {
await saveOutsideServices(vm, i);
}
}
}
//####################################################################################
//## GRANDCHILDREN
/////////////////////////////
// UNITS
//
async function deleteUnits(vm, woItemIndex) {
//walk the array backwards as items may be spliced out
for (var i = vm.obj.items[woItemIndex].units.length - 1; i >= 0; i--) {
const d = vm.obj.items[woItemIndex].units[i];
if (!d.deleted) {
continue;
}
if (d.id != 0) {
const res = await window.$gz.api.remove(
`${API_BASE_URL}items/units/${d.id}`
);
if (res.error) {
handleSaveError(vm, {
error: res.error,
itemUid: vm.obj.items[woItemIndex].uid,
childKey: "units",
childUid: d.uid
});
return;
}
}
vm.obj.items[woItemIndex].units.splice(i, 1);
}
//----
return;
}
async function saveUnits(vm, woItemIndex) {
//DELETE FLAGGED ITEMS FIRST
await deleteUnits(vm, woItemIndex);
if (vm.saveResult.fatal) {
return;
}
for (let i = 0; i < vm.obj.items[woItemIndex].units.length; i++) {
if (vm.obj.items[woItemIndex].units[i].isDirty) {
//clone and skip viz and other fields
const o = window.$gz.util.deepCopySkip(
vm.obj.items[woItemIndex].units[i],
["uid", "isDirty"]
);
const res = await window.$gz.api.upsert(`${API_BASE_URL}items/units`, o);
if (res.error) {
handleSaveError(vm, {
error: res.error,
itemUid: vm.obj.items[woItemIndex].uid,
childKey: "units",
childUid: vm.obj.items[woItemIndex].units[i].uid
});
} else {
//Server will update fields on put or post for most workorder graph objecs so need to update entire object here
res.data.isDirty = false; //prime isDirty to detect future edits
res.data.warrantyViz = null; //Add local key so it's reactive in vue
res.data.uid = i; //set uid again as it's lost in the save
vm.obj.items[woItemIndex].units.splice(i, 1, res.data);
}
}
}
return; //made it
}
/////////////////////////////
// SCHEDULED USERS
//
async function saveScheduledUsers(vm, woItemIndex) {
//DELETE FLAGGED ITEMS FIRST
await deleteScheduledUsers(vm, woItemIndex);
if (vm.saveResult.fatal) {
return;
}
for (let i = 0; i < vm.obj.items[woItemIndex].scheduledUsers.length; i++) {
if (vm.obj.items[woItemIndex].scheduledUsers[i].isDirty) {
//clone and skip viz and other fields
const o = window.$gz.util.deepCopySkip(
vm.obj.items[woItemIndex].scheduledUsers[i],
["uid", "isDirty"]
);
const res = await window.$gz.api.upsert(
`${API_BASE_URL}items/scheduled-users`,
o
);
if (res.error) {
handleSaveError(vm, {
error: res.error,
itemUid: vm.obj.items[woItemIndex].uid,
childKey: "scheduledUsers",
childUid: vm.obj.items[woItemIndex].scheduledUsers[i].uid
});
} else {
//Server will update fields on put or post for most workorder graph objecs so need to update entire object here
res.data.isDirty = false; //prime isDirty to detect future edits
res.data.uid = i; //set uid again as it's lost in the save
vm.obj.items[woItemIndex].scheduledUsers.splice(i, 1, res.data);
}
}
}
return; //made it
}
async function deleteScheduledUsers(vm, woItemIndex) {
//walk the array backwards as items may be spliced out
for (
var i = vm.obj.items[woItemIndex].scheduledUsers.length - 1;
i >= 0;
i--
) {
const d = vm.obj.items[woItemIndex].scheduledUsers[i];
if (!d.deleted) {
continue;
}
if (d.id != 0) {
const res = await window.$gz.api.remove(
`${API_BASE_URL}items/scheduled-users/${d.id}`
);
if (res.error) {
handleSaveError(vm, {
error: res.error,
itemUid: vm.obj.items[woItemIndex].uid,
childKey: "scheduledUsers",
childUid: d.uid
});
return;
}
}
vm.obj.items[woItemIndex].scheduledUsers.splice(i, 1);
}
//----
return;
}
/////////////////////////////
// TASKS
//
async function saveTasks(vm, woItemIndex) {
//DELETE FLAGGED ITEMS FIRST
await deleteTasks(vm, woItemIndex);
if (vm.saveResult.fatal) {
return;
}
for (let i = 0; i < vm.obj.items[woItemIndex].tasks.length; i++) {
if (vm.obj.items[woItemIndex].tasks[i].isDirty) {
//clone and skip viz and other fields
const o = window.$gz.util.deepCopySkip(
vm.obj.items[woItemIndex].tasks[i],
["uid", "isDirty"]
);
const res = await window.$gz.api.upsert(`${API_BASE_URL}items/tasks`, o);
if (res.error) {
handleSaveError(vm, {
error: res.error,
itemUid: vm.obj.items[woItemIndex].uid,
childKey: "tasks",
childUid: vm.obj.items[woItemIndex].tasks[i].uid
});
} else {
//Server will update fields on put or post for most workorder graph objecs so need to update entire object here
res.data.isDirty = false; //prime isDirty to detect future edits
res.data.uid = i; //set uid again as it's lost in the save
vm.obj.items[woItemIndex].tasks.splice(i, 1, res.data);
}
}
}
return; //made it
}
async function deleteTasks(vm, woItemIndex) {
//walk the array backwards as items may be spliced out
for (var i = vm.obj.items[woItemIndex].tasks.length - 1; i >= 0; i--) {
const d = vm.obj.items[woItemIndex].tasks[i];
if (!d.deleted) {
continue;
}
if (d.id != 0) {
const res = await window.$gz.api.remove(
`${API_BASE_URL}items/tasks/${d.id}`
);
if (res.error) {
handleSaveError(vm, {
error: res.error,
itemUid: vm.obj.items[woItemIndex].uid,
childKey: "tasks",
childUid: d.uid
});
return;
}
}
vm.obj.items[woItemIndex].tasks.splice(i, 1);
}
//----
return;
}
/////////////////////////////
// PARTS
//
async function deleteParts(vm, woItemIndex) {
//walk the array backwards as items may be spliced out
for (var i = vm.obj.items[woItemIndex].parts.length - 1; i >= 0; i--) {
const d = vm.obj.items[woItemIndex].parts[i];
if (!d.deleted) {
continue;
}
if (d.id != 0) {
const res = await window.$gz.api.remove(
`${API_BASE_URL}items/parts/${d.id}`
);
if (res.error) {
handleSaveError(vm, {
error: res.error,
itemUid: vm.obj.items[woItemIndex].uid,
childKey: "parts",
childUid: d.uid
});
return;
}
}
vm.obj.items[woItemIndex].parts.splice(i, 1);
}
//----
return;
}
async function saveParts(vm, woItemIndex) {
//DELETE FLAGGED ITEMS FIRST
await deleteParts(vm, woItemIndex);
if (vm.saveResult.fatal) {
return;
}
for (let i = 0; i < vm.obj.items[woItemIndex].parts.length; i++) {
if (vm.obj.items[woItemIndex].parts[i].isDirty) {
//clone and skip viz and other fields
const o = window.$gz.util.deepCopySkip(
vm.obj.items[woItemIndex].parts[i],
["uid", "isDirty"]
);
const res = await window.$gz.api.upsert(`${API_BASE_URL}items/parts`, o);
if (res.error) {
//insufficient stock?
if (res.error.details) {
const balanceError = res.error.details.find(z => z.error == "2040");
if (balanceError && balanceError.message) {
//set the amount requestable so it surfaces in the UI and can be requested
const balance = window.$gz.util.stringToFloat(balanceError.message);
if (balance != null || balance != 0) {
vm.obj.items[woItemIndex].parts[i].requestAmountViz =
vm.obj.items[woItemIndex].parts[i].quantity - balance;
}
}
}
handleSaveError(vm, {
error: res.error,
itemUid: vm.obj.items[woItemIndex].uid,
childKey: "parts",
childUid: vm.obj.items[woItemIndex].parts[i].uid
});
} else {
//Server will update fields on put or post for most workorder graph objecs so need to update entire object here
res.data.isDirty = false; //prime isDirty to detect future edits
res.data.uid = i; //set uid again as it's lost in the save
vm.obj.items[woItemIndex].parts.splice(i, 1, res.data);
}
}
}
return; //made it
}
/////////////////////////////
// PART REQUESTS
//
async function deletePartRequests(vm, woItemIndex) {
//walk the array backwards as items may be spliced out
for (var i = vm.obj.items[woItemIndex].partRequests.length - 1; i >= 0; i--) {
const d = vm.obj.items[woItemIndex].partRequests[i];
if (!d.deleted) {
continue;
}
if (d.id != 0) {
const res = await window.$gz.api.remove(
`${API_BASE_URL}items/part-requests/${d.id}`
);
if (res.error) {
handleSaveError(vm, {
error: res.error,
itemUid: vm.obj.items[woItemIndex].uid,
childKey: "partRequests",
childUid: d.uid
});
return;
}
}
vm.obj.items[woItemIndex].partRequests.splice(i, 1);
}
//----
return;
}
async function savePartRequests(vm, woItemIndex) {
//DELETE FLAGGED ITEMS FIRST
await deletePartRequests(vm, woItemIndex);
if (vm.saveResult.fatal) {
return;
}
for (let i = 0; i < vm.obj.items[woItemIndex].partRequests.length; i++) {
if (vm.obj.items[woItemIndex].partRequests[i].isDirty) {
//clone and skip viz and other fields
const o = window.$gz.util.deepCopySkip(
vm.obj.items[woItemIndex].partRequests[i],
["uid", "isDirty"]
);
const res = await window.$gz.api.upsert(
`${API_BASE_URL}items/part-requests`,
o
);
if (res.error) {
handleSaveError(vm, {
error: res.error,
itemUid: vm.obj.items[woItemIndex].uid,
childKey: "partRequests",
childUid: vm.obj.items[woItemIndex].partRequests[i].uid
});
} else {
//Server will update fields on put or post for most workorder graph objecs so need to update entire object here
res.data.isDirty = false; //prime isDirty to detect future edits
res.data.uid = i; //set uid again as it's lost in the save
vm.obj.items[woItemIndex].partRequests.splice(i, 1, res.data);
}
}
}
return; //made it
}
/////////////////////////////
// LABOR
//
async function saveLabors(vm, woItemIndex) {
//DELETE FLAGGED ITEMS FIRST
await deleteLabors(vm, woItemIndex);
if (vm.saveResult.fatal) {
return;
}
for (let i = 0; i < vm.obj.items[woItemIndex].labors.length; i++) {
if (vm.obj.items[woItemIndex].labors[i].isDirty) {
//clone and skip viz and other fields
const o = window.$gz.util.deepCopySkip(
vm.obj.items[woItemIndex].labors[i],
["uid", "isDirty"]
);
const res = await window.$gz.api.upsert(`${API_BASE_URL}items/labors`, o);
if (res.error) {
handleSaveError(vm, {
error: res.error,
itemUid: vm.obj.items[woItemIndex].uid,
childKey: "labors",
childUid: vm.obj.items[woItemIndex].labors[i].uid
});
} else {
//Server will update fields on put or post for most workorder graph objecs so need to update entire object here
res.data.isDirty = false; //prime isDirty to detect future edits
res.data.uid = i; //set uid again as it's lost in the save
vm.obj.items[woItemIndex].labors.splice(i, 1, res.data);
}
}
}
return;
}
async function deleteLabors(vm, woItemIndex) {
//walk the array backwards as items may be spliced out
for (var i = vm.obj.items[woItemIndex].labors.length - 1; i >= 0; i--) {
const d = vm.obj.items[woItemIndex].labors[i];
if (!d.deleted) {
continue;
}
if (d.id != 0) {
const res = await window.$gz.api.remove(
`${API_BASE_URL}items/labors/${d.id}`
);
if (res.error) {
handleSaveError(vm, {
error: res.error,
itemUid: vm.obj.items[woItemIndex].uid,
childKey: "labors",
childUid: d.uid
});
return;
}
}
vm.obj.items[woItemIndex].labors.splice(i, 1);
}
//----
return;
}
/////////////////////////////
// TRAVEL
//
async function saveTravels(vm, woItemIndex) {
//DELETE FLAGGED ITEMS FIRST
await deleteTravels(vm, woItemIndex);
if (vm.saveResult.fatal) {
return;
}
for (let i = 0; i < vm.obj.items[woItemIndex].travels.length; i++) {
if (vm.obj.items[woItemIndex].travels[i].isDirty) {
//clone and skip viz and other fields
const o = window.$gz.util.deepCopySkip(
vm.obj.items[woItemIndex].travels[i],
["uid", "isDirty"]
);
const res = await window.$gz.api.upsert(
`${API_BASE_URL}items/travels`,
o
);
if (res.error) {
handleSaveError(vm, {
error: res.error,
itemUid: vm.obj.items[woItemIndex].uid,
childKey: "travels",
childUid: vm.obj.items[woItemIndex].travels[i].uid
});
} else {
//Server will update fields on put or post for most workorder graph objecs so need to update entire object here
res.data.isDirty = false; //prime isDirty to detect future edits
res.data.uid = i; //set uid again as it's lost in the save
vm.obj.items[woItemIndex].travels.splice(i, 1, res.data);
}
}
}
return;
}
async function deleteTravels(vm, woItemIndex) {
//walk the array backwards as items may be spliced out
for (var i = vm.obj.items[woItemIndex].travels.length - 1; i >= 0; i--) {
const d = vm.obj.items[woItemIndex].travels[i];
if (!d.deleted) {
continue;
}
if (d.id != 0) {
const res = await window.$gz.api.remove(
`${API_BASE_URL}items/travels/${d.id}`
);
if (res.error) {
handleSaveError(vm, {
error: res.error,
itemUid: vm.obj.items[woItemIndex].uid,
childKey: "travels",
childUid: d.uid
});
return;
}
}
vm.obj.items[woItemIndex].travels.splice(i, 1);
}
//----
return;
}
/////////////////////////////
// EXPENSES
//
async function deleteExpenses(vm, woItemIndex) {
//walk the array backwards as items may be spliced out
for (var i = vm.obj.items[woItemIndex].expenses.length - 1; i >= 0; i--) {
const d = vm.obj.items[woItemIndex].expenses[i];
if (!d.deleted) {
continue;
}
if (d.id != 0) {
const res = await window.$gz.api.remove(
`${API_BASE_URL}items/expenses/${d.id}`
);
if (res.error) {
handleSaveError(vm, {
error: res.error,
itemUid: vm.obj.items[woItemIndex].uid,
childKey: "expenses",
childUid: d.uid
});
return;
}
}
vm.obj.items[woItemIndex].expenses.splice(i, 1);
}
//----
return;
}
async function saveExpenses(vm, woItemIndex) {
//DELETE FLAGGED ITEMS FIRST
await deleteExpenses(vm, woItemIndex);
if (vm.saveResult.fatal) {
return;
}
for (let i = 0; i < vm.obj.items[woItemIndex].expenses.length; i++) {
if (vm.obj.items[woItemIndex].expenses[i].isDirty) {
//clone and skip viz and other fields
const o = window.$gz.util.deepCopySkip(
vm.obj.items[woItemIndex].expenses[i],
["uid", "isDirty"]
);
const res = await window.$gz.api.upsert(
`${API_BASE_URL}items/expenses`,
o
);
if (res.error) {
handleSaveError(vm, {
error: res.error,
itemUid: vm.obj.items[woItemIndex].uid,
childKey: "expenses",
childUid: vm.obj.items[woItemIndex].expenses[i].uid
});
} else {
//Server will update fields on put or post for most workorder graph objecs so need to update entire object here
res.data.isDirty = false; //prime isDirty to detect future edits
res.data.uid = i; //set uid again as it's lost in the save
vm.obj.items[woItemIndex].expenses.splice(i, 1, res.data);
}
}
}
return; //made it
}
/////////////////////////////
// LOANS
//
async function deleteLoans(vm, woItemIndex) {
//walk the array backwards as items may be spliced out
for (var i = vm.obj.items[woItemIndex].loans.length - 1; i >= 0; i--) {
const d = vm.obj.items[woItemIndex].loans[i];
if (!d.deleted) {
continue;
}
if (d.id != 0) {
const res = await window.$gz.api.remove(
`${API_BASE_URL}items/loans/${d.id}`
);
if (res.error) {
handleSaveError(vm, {
error: res.error,
itemUid: vm.obj.items[woItemIndex].uid,
childKey: "loans",
childUid: d.uid
});
return;
}
}
vm.obj.items[woItemIndex].loans.splice(i, 1);
}
//----
return;
}
async function saveLoans(vm, woItemIndex) {
//DELETE FLAGGED ITEMS FIRST
await deleteLoans(vm, woItemIndex);
if (vm.saveResult.fatal) {
return;
}
for (let i = 0; i < vm.obj.items[woItemIndex].loans.length; i++) {
if (vm.obj.items[woItemIndex].loans[i].isDirty) {
//clone and skip viz and other fields
const o = window.$gz.util.deepCopySkip(
vm.obj.items[woItemIndex].loans[i],
["uid", "isDirty"]
);
const res = await window.$gz.api.upsert(`${API_BASE_URL}items/loans`, o);
if (res.error) {
handleSaveError(vm, {
error: res.error,
itemUid: vm.obj.items[woItemIndex].uid,
childKey: "loans",
childUid: vm.obj.items[woItemIndex].loans[i].uid
});
} else {
//Server will update fields on put or post for most workorder graph objecs so need to update entire object here
res.data.isDirty = false; //prime isDirty to detect future edits
res.data.uid = i; //set uid again as it's lost in the save
vm.obj.items[woItemIndex].loans.splice(i, 1, res.data);
}
}
}
return; //made it
}
/////////////////////////////
// OUTSIDE SERVICES
//
async function deleteOutsideServices(vm, woItemIndex) {
//walk the array backwards as items may be spliced out
for (
var i = vm.obj.items[woItemIndex].outsideServices.length - 1;
i >= 0;
i--
) {
const d = vm.obj.items[woItemIndex].outsideServices[i];
if (!d.deleted) {
continue;
}
if (d.id != 0) {
const res = await window.$gz.api.remove(
`${API_BASE_URL}items/outside-services/${d.id}`
);
if (res.error) {
handleSaveError(vm, {
error: res.error,
itemUid: vm.obj.items[woItemIndex].uid,
childKey: "outsideServices",
childUid: d.uid
});
return;
}
}
vm.obj.items[woItemIndex].outsideServices.splice(i, 1);
}
//----
return;
}
async function saveOutsideServices(vm, woItemIndex) {
//DELETE FLAGGED ITEMS FIRST
await deleteOutsideServices(vm, woItemIndex);
if (vm.saveResult.fatal) {
return;
}
for (let i = 0; i < vm.obj.items[woItemIndex].outsideServices.length; i++) {
if (vm.obj.items[woItemIndex].outsideServices[i].isDirty) {
//clone and skip viz and other fields
const o = window.$gz.util.deepCopySkip(
vm.obj.items[woItemIndex].outsideServices[i],
["uid", "isDirty"]
);
const res = await window.$gz.api.upsert(
`${API_BASE_URL}items/outside-services`,
o
);
if (res.error) {
handleSaveError(vm, {
error: res.error,
itemUid: vm.obj.items[woItemIndex].uid,
childKey: "outsideServices",
childUid: vm.obj.items[woItemIndex].outsideServices[i].uid
});
} else {
//Server will update fields on put or post for most workorder graph objecs so need to update entire object here
res.data.isDirty = false; //prime isDirty to detect future edits
res.data.uid = i; //set uid again as it's lost in the save
vm.obj.items[woItemIndex].outsideServices.splice(i, 1, res.data);
}
}
}
return; //made it
}
//######################################### UTILITY METHODS ###########################################
function handleSaveError(vm, e) {
//### NOTE: Nothing using FATAL yet, but if necessary here is where it should be determined and set
if (vm.saveResult.errors == null) {
vm.saveResult.errors = [];
}
vm.saveResult.errors.push(e);
}
function errorTargetFromSaveResult(vm, r, t) {
//no particular target
//or not a woitem tree error (header)
if (r.itemUid == null || t == null || t == "generalerror") {
return t;
}
const woitemindex = vm.obj.items.findIndex(z => z.uid == r.itemUid);
if (woitemindex == -1) {
return null;
}
if (r.childKey == null || r.childUid == null) {
return `Items[${woitemindex}].${t}`;
}
const childindex = vm.obj.items[woitemindex][r.childKey].findIndex(
z => z.uid == r.childUid
);
if (childindex == -1) {
return null;
}
return `Items[${woitemindex}].${r.childKey}[${childindex}].${t}`;
}
function formErrorFromSaveResult(vm) {
/**
* convention:
* {
error: res.error,
itemUid: item.uid, //uid of item none means it's a header error
childKey: "scheduledUsers",
childUid: i //uid of child item will be located later as index
}
*
*/
//Note: re code 2200 hard coded here below -- if for some reason it becomes necessary there is the idea of making an envelope error to contain variety of errors,
//but so far I think they are all server errors (that go in the error box) or validation errors that work here
let ret = {
code: "2200",
details: [],
message: "ErrorAPI2200"
};
vm.saveResult.errors.forEach(z => {
if (z.error.details != null) {
z.error.details.forEach(x => {
const target = errorTargetFromSaveResult(vm, z, x.target);
if (target != null) {
ret.details.push({
message: x.message,
error: x.error,
target: target
});
}
});
} else {
ret = z.error;
}
});
return ret;
}
/////////////////////////////
//
//
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;
}
const 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 }
});
break;
case "duplicate":
m.vm.duplicateDlgTitle = m.vm.$ay.t("DuplicateToWorkOrder");
m.vm.duplicateTo = "wo";
m.vm.duplicateDlg = true;
break;
case "genquote":
m.vm.duplicateDlgTitle = m.vm.$ay.t("DuplicateToQuote");
m.vm.duplicateTo = "quote";
m.vm.duplicateDlg = true;
break;
case "genpm":
m.vm.duplicateDlgTitle = m.vm.$ay.t("DuplicateToPM");
m.vm.duplicateTo = "pm";
m.vm.duplicateDlg = true;
break;
case "report":
{
const res = await m.vm.$refs.reportSelector.open(
{
AType: window.$gz.type.WorkOrder,
selectedRowIds: [m.vm.obj.id]
},
m.id
);
if (res == null) {
return;
}
window.$gz.form.setLastReportMenuItem(FORM_KEY, res, m.vm);
}
break;
case "statuslist":
m.vm.$router.push({
name: "svc-work-order-status"
});
break;
case "itemstatuslist":
m.vm.$router.push({
name: "svc-work-order-item-status"
});
break;
case "prioritylist":
m.vm.$router.push({
name: "svc-work-order-item-priorities"
});
break;
case "taskgroupslist":
m.vm.$router.push({
name: "svc-task-groups"
});
break;
case "quote":
window.$gz.eventBus.$emit("openobject", {
type: window.$gz.type.Quote,
id: m.vm.obj.fromQuoteId
});
break;
case "pm":
window.$gz.eventBus.$emit("openobject", {
type: window.$gz.type.PM,
id: m.vm.obj.fromPMId
});
break;
case "geoview":
window.$gz.util.viewGeoLocation({
latitude: m.vm.obj.latitude,
longitude: m.vm.obj.longitude,
address: m.vm.obj.address || m.vm.obj.postAddress,
city: m.vm.obj.city || m.vm.obj.postCity,
region: m.vm.obj.region || m.vm.obj.postRegion,
country: m.vm.obj.country || m.vm.obj.postCountry,
postCode: m.vm.obj.postCode
});
break;
default:
window.$gz.eventBus.$emit(
"notify-warning",
FORM_KEY + "::context click: [" + m.key + "]"
);
}
}
}
//////////////////////
//
//
function generateMenu(vm) {
const menuOptions = {
isMain: false,
readOnly: vm.formState.readOnly,
hideCoreBizStandardOptions: vm.obj.userIsRestrictedType,
hideSearch: vm.obj.userIsRestrictedType,
icon: "$ayiTools",
title: "WorkOrder",
helpUrl: "svc-workorders",
formData: {
ayaType: window.$gz.type.WorkOrder,
recordId: vm.$route.params.recordid,
formCustomTemplateKey: FORM_CUSTOM_TEMPLATE_KEY,
recordName: vm.obj.serial == 0 ? null : vm.obj.serial.toString()
},
menuItems: []
};
if (vm.rights.change) {
menuOptions.menuItems.push({
title: "Save",
icon: "$ayiSave",
surface: true,
key: FORM_KEY + ":save",
vm: vm
});
}
menuOptions.menuItems.push({
title: "Report",
icon: "$ayiFileAlt",
key: FORM_KEY + ":report",
vm: vm
});
const lastReport = window.$gz.form.getLastReport(FORM_KEY);
if (lastReport != null && !vm.obj.userIsRestrictedType) {
menuOptions.menuItems.push({
title: lastReport.name,
notrans: true,
icon: "$ayiFileAlt",
key: FORM_KEY + ":report:" + lastReport.id,
vm: vm
});
}
if (vm.rights.change && !vm.obj.userIsRestrictedType) {
menuOptions.menuItems.push({
title: "New",
icon: "$ayiPlus",
key: FORM_KEY + ":new",
vm: vm
});
}
if (
vm.rights.change &&
vm.$route.params.recordid != 0 &&
!vm.obj.userIsRestrictedType
) {
menuOptions.menuItems.push({
title: "Duplicate",
icon: "$ayiClone",
key: FORM_KEY + ":duplicate",
vm: vm
});
}
if (
vm.rights.change &&
vm.$route.params.recordid != 0 &&
!vm.obj.userIsRestrictedType
) {
menuOptions.menuItems.push({
title: "DuplicateToQuote",
icon: "$ayiPencilAlt",
key: FORM_KEY + ":genquote",
vm: vm
});
}
if (
vm.rights.change &&
vm.$route.params.recordid != 0 &&
!vm.obj.userIsRestrictedType
) {
menuOptions.menuItems.push({
title: "DuplicateToPM",
icon: "$ayiBusinessTime",
key: FORM_KEY + ":genpm",
vm: vm
});
}
if (
vm.rights.delete &&
vm.$route.params.recordid != 0 &&
!vm.obj.userIsRestrictedType
) {
menuOptions.menuItems.push({
title: "Delete",
icon: "$ayiTrashAlt",
surface: false,
key: FORM_KEY + ":delete",
vm: vm
});
}
//--- /show all ---
if (!vm.obj.userIsRestrictedType) {
menuOptions.menuItems.push({ divider: true, inset: false });
menuOptions.menuItems.push({
title: "WorkOrderStatusList",
icon: "$ayiFlag",
key: FORM_KEY + ":statuslist",
vm: vm
});
menuOptions.menuItems.push({
title: "WorkOrderItemStatusList",
icon: "$ayiCircle",
key: FORM_KEY + ":itemstatuslist",
vm: vm
});
menuOptions.menuItems.push({
title: "WorkOrderItemPriorityList",
icon: "$ayiFireAlt",
key: FORM_KEY + ":prioritylist",
vm: vm
});
menuOptions.menuItems.push({
title: "TaskGroupList",
icon: "$ayiTasks",
key: FORM_KEY + ":taskgroupslist",
vm: vm
});
}
menuOptions.menuItems.push({
title: "GeoView",
icon: "$ayiMapMarked",
key: FORM_KEY + ":geoview",
vm: vm
});
menuOptions.menuItems.push({ divider: true, inset: false });
let hasFromInsertDivider = false;
if (
vm.obj.fromQuoteId != null &&
window.$gz.role.canOpen(window.$gz.type.Quote)
) {
menuOptions.menuItems.push({
title: "WorkOrderFromQuoteID",
icon: "$ayiPencilAlt",
key: FORM_KEY + ":quote",
vm: vm
});
hasFromInsertDivider = true;
}
if (vm.obj.fromPMId != null && window.$gz.role.canOpen(window.$gz.type.PM)) {
menuOptions.menuItems.push({
title: "WorkOrderFromPMID",
icon: "$ayiBusinessTime",
key: FORM_KEY + ":pm",
vm: vm
});
hasFromInsertDivider = true;
}
if (hasFromInsertDivider) {
menuOptions.menuItems.push({ divider: true, inset: false });
}
window.$gz.eventBus.$emit("menu-change", menuOptions);
}
//####################################################
let JUST_DELETED = false;
/////////////////////////////////
//
//
async function initForm(vm) {
await fetchTranslatedText();
await window.$gz.formCustomTemplate.get(FORM_CUSTOM_TEMPLATE_KEY, vm);
await populateSelectionLists(vm);
}
//////////////////////////////////////////////////////////
//
// Ensures UI translated text is available
//
async function fetchTranslatedText() {
await window.$gz.translation.cacheTranslations([
"WorkOrder",
"CopyWiki",
"CopyAttachments",
"Customer",
"WorkOrderSerialNumber",
"WorkOrderSummary",
"WorkOrderCloseByDate",
"Contract",
"Project",
"WorkOrderInvoiceNumber",
"WorkOrderServiceDate",
"WorkOrderCustomerContactName",
"WorkOrderCustomerReferenceNumber",
"WorkOrderInternalReferenceNumber",
"WorkOrderOnsite",
"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",
"Sequence",
"WorkOrderItemCustom1",
"WorkOrderItemCustom2",
"WorkOrderItemCustom3",
"WorkOrderItemCustom4",
"WorkOrderItemCustom5",
"WorkOrderItemCustom6",
"WorkOrderItemCustom7",
"WorkOrderItemCustom8",
"WorkOrderItemCustom9",
"WorkOrderItemCustom10",
"WorkOrderItemCustom11",
"WorkOrderItemCustom12",
"WorkOrderItemCustom13",
"WorkOrderItemCustom14",
"WorkOrderItemCustom15",
"WorkOrderItemCustom16",
"AddressTypePhysical",
"AddressTypePostal",
"AddressCopyToPostal",
"AddressCopyToPhysical",
"Address",
"AddressPostalDeliveryAddress",
"AddressPostalCity",
"AddressPostalStateProv",
"AddressPostalCountry",
"AddressPostalPostal",
"AddressDeliveryAddress",
"AddressCity",
"AddressStateProv",
"AddressCountry",
"AddressLatitude",
"AddressLongitude",
"SelectAlternateAddress",
"WorkOrderItemStatusList",
"WorkOrderStatusList",
"WorkOrderItemPriorityList",
"WorkOrderItemUnitList",
"WorkOrderItemUnitNotes",
"Unit",
"WorkOrderItemUnitCustom1",
"WorkOrderItemUnitCustom2",
"WorkOrderItemUnitCustom3",
"WorkOrderItemUnitCustom4",
"WorkOrderItemUnitCustom5",
"WorkOrderItemUnitCustom6",
"WorkOrderItemUnitCustom7",
"WorkOrderItemUnitCustom8",
"WorkOrderItemUnitCustom9",
"WorkOrderItemUnitCustom10",
"WorkOrderItemUnitCustom11",
"WorkOrderItemUnitCustom12",
"WorkOrderItemUnitCustom13",
"WorkOrderItemUnitCustom14",
"WorkOrderItemUnitCustom15",
"WorkOrderItemUnitCustom16",
"WorkOrderItemExpenseChargeAmount",
"WorkOrderItemExpenseChargeTaxCodeID",
"WorkOrderItemExpenseChargeToCustomer",
"WorkOrderItemExpenseDescription",
"WorkOrderItemExpenseList",
"WorkOrderItemExpenseName",
"WorkOrderItemExpenseReimburseUser",
"WorkOrderItemExpenseTaxPaid",
"WorkOrderItemExpenseTotalCost",
"WorkOrderItemExpenseUserID",
"WorkOrderItemLaborList",
"WorkOrderItemLaborServiceStartDate",
"WorkOrderItemLaborServiceStopDate",
"WorkOrderItemLaborServiceRateQuantity",
"WorkOrderItemLaborServiceRateID",
"WorkOrderItemLaborServiceDetails",
"WorkOrderItemLaborUserID",
"WorkOrderItemLaborNoChargeQuantity",
"WorkOrderItemLaborTaxRateSaleID",
"WorkOrderItemLoanNotes",
"WorkOrderItemLoanOutDate",
"WorkOrderItemLoanDueDate",
"WorkOrderItemLoanReturnDate",
"WorkOrderItemLoanTaxCodeID",
"WorkOrderItemLoanUnit",
"WorkOrderItemLoanQuantity",
"WorkOrderItemLoanRate",
"WorkOrderItemLoanList",
"WorkOrderItemTravelList",
"WorkOrderItemTravelStartDate",
"WorkOrderItemTravelStopDate",
"WorkOrderItemTravelRateQuantity",
"WorkOrderItemTravelDistance",
"WorkOrderItemTravelServiceRateID",
"WorkOrderItemTravelDetails",
"WorkOrderItemTravelUserID",
"WorkOrderItemTravelNoChargeQuantity",
"WorkOrderItemTravelTaxRateSaleID",
"WorkOrderItemTaskTaskID",
"WorkOrderItemTaskWorkOrderItemTaskCompletionType",
"WorkOrderItemTaskUser",
"WorkOrderItemTaskCompletedDate",
"WorkOrderItemTasks",
"WorkOrderItemPartList",
"WorkOrderItemPartDescription",
"PurchaseOrderItemSerialNumbers",
"WorkOrderItemPartPartWarehouseID",
"WorkOrderItemPartQuantity",
"WorkOrderItemPartSuggestedQuantity",
"WorkOrderItemPartRealizeSuggested",
"WorkOrderItemPartTaxPartSaleID",
"WorkOrderItemPartPartID",
"WorkOrderItemPartRequestList",
"WorkOrderItemPartRequestPartID",
"WorkOrderItemPartRequestPartWarehouseID",
"WorkOrderItemPartRequestQuantity",
"WorkOrderItemPartRequestMore",
"PurchaseOrder",
"PurchaseOrderExpectedReceiveDate",
"PurchaseOrderOrderedDate",
"WorkOrderItemPartRequestOnOrder",
"WorkOrderItemPartRequestReceived",
"OutsideServiceList",
"WorkOrderItemOutsideServiceNotes",
"WorkOrderItemOutsideServiceVendorSentToID",
"WorkOrderItemOutsideServiceVendorSentViaID",
"WorkOrderItemOutsideServiceRMANumber",
"WorkOrderItemOutsideServiceTrackingNumber",
"WorkOrderItemOutsideServiceRepairCost",
"WorkOrderItemOutsideServiceRepairPrice",
"WorkOrderItemOutsideServiceShippingCost",
"WorkOrderItemOutsideServiceShippingPrice",
"WorkOrderItemOutsideServiceDateSent",
"WorkOrderItemOutsideServiceDateETA",
"WorkOrderItemOutsideServiceDateReturned",
"TaxCode",
"PartDescription",
"PartUPC",
"TaskGroup",
"TaskGroupList",
"SaveRecordToProceed",
"Cost",
"ListPrice",
"NetPrice",
"Price",
"PriceOverride",
"Tax",
"TaxAAmt",
"TaxBAmt",
"LineTotal",
"UnitOfMeasure",
"WorkOrderItemPart",
"WorkOrderItemLabor",
"WorkOrderItemTravel",
"WorkOrderItemExpense",
"WorkOrderItemScheduledUser",
"WorkOrderItemUnit",
"WorkOrderItemTask",
"WorkOrderItemLoan",
"WorkOrderItemOutsideService",
"CustomerSignature",
"Name",
"TechSignature",
"PartAssemblyList",
"PartAssembly",
"PartSerialNumbersAvailable",
"AddMultipleUnits",
"CopyToWorkOrder",
"WorkOrderConvertScheduledUserToLabor",
"WorkOrderConvertAllScheduledUsersToLabor",
"AppendTasks",
"UnitWarrantyInfo",
"UnitModelLifeTimeWarranty",
"UnitModelVendorID",
"UnitModelName",
"UnitDescription",
"Warranty",
"WarrantyExpires",
"UnitPurchaseFromID",
"UnitPurchasedDate",
"UnitReceipt",
"RecentWorkOrders",
"WorkOrderGenerateUnit",
"ApplyUnitContract",
"WorkOrderFromQuoteID",
"WorkOrderFromPMID",
"CustomerServiceRequest",
"DuplicateToWorkOrder",
"DuplicateToQuote",
"DuplicateToPM",
"NewStatus",
"CustomerTechNotes",
"RemoveRoles",
"SelectRoles"
]);
}
//////////////////////
//
//
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);
} else {
vm.selectLists.wostatus = res.data.all;
vm.selectLists.allowedwostatus = res.data.allowed;
}
res = await window.$gz.api.get("work-order-item-status/list");
if (res.error) {
vm.formState.serverError = res.error;
window.$gz.form.setErrorBoxErrors(vm);
} else {
vm.selectLists.woItemStatus = res.data;
}
res = await window.$gz.api.get("work-order-item-priority/list");
if (res.error) {
vm.formState.serverError = res.error;
window.$gz.form.setErrorBoxErrors(vm);
} else {
vm.selectLists.woItemPriorities = res.data;
}
await window.$gz.enums.fetchEnumList("WorkOrderItemTaskCompletionType"); //prefetch
vm.selectLists.woItemTaskCompletionTypes = window.$gz.enums.getSelectionList(
"WorkOrderItemTaskCompletionType"
);
await window.$gz.enums.fetchEnumList("LoanUnitRateUnit"); //prefetch
vm.selectLists.loanUnitRateUnits = window.$gz.enums.getSelectionList(
"LoanUnitRateUnit"
);
//---------
}
</script>