This commit is contained in:
2021-07-26 21:52:09 +00:00
parent 29cd9e8541
commit 41303e7726
13 changed files with 8426 additions and 86 deletions

View File

@@ -37,28 +37,7 @@
@input="fieldValueChanged('customerId')"
></gz-pick-list>
</v-col>
<v-col
v-if="
form().showMe(this, 'CustomerSignature') &&
!(
value.userIsSubContractorFull ||
value.userIsSubContractorRestricted
)
"
cols="12"
sm="6"
lg="4"
xl="3"
>
<GzWoSignature
v-model="value"
:form-key="formCustomTemplateKey"
:readonly="formState.readOnly || value.userIsTechRestricted"
:pvm="pvm"
variant="customer"
data-cy="customerSignature"
/>
</v-col>
<v-col
v-if="
form().showMe(this, 'WorkOrderStatus') &&
@@ -72,14 +51,14 @@
lg="4"
xl="3"
>
<GzWoState
<GzQuoteState
v-model="value"
:form-key="formCustomTemplateKey"
:readonly="formState.readOnly"
:pvm="pvm"
data-cy="woState"
:all-states="pvm.selectLists.wostatus"
:allowed-states="pvm.selectLists.allowedwostatus"
:all-states="pvm.selectLists.quotestatus"
:allowed-states="pvm.selectLists.allowedquotestatus"
/>
</v-col>
@@ -101,29 +80,6 @@
/>
</v-col>
<v-col
v-if="
form().showMe(this, 'TechSignature') &&
!(
value.userIsSubContractorFull ||
value.userIsSubContractorRestricted
)
"
cols="12"
sm="6"
lg="4"
xl="3"
>
<GzWoSignature
v-model="value"
:form-key="formCustomTemplateKey"
:readonly="formState.readOnly || value.userIsTechRestricted"
:pvm="pvm"
variant="tech"
data-cy="techSignature"
/>
</v-col>
<v-col
v-if="
form().showMe(this, 'WorkOrderSummary') &&
@@ -450,14 +406,12 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/* XXXeslint-disable */
////////////////////////////////////////////////////////////////////////////////////////////////////////////
import GzWoState from "./work-order-state.vue";
import GzQuoteState from "./quote-state.vue";
import GzWoAddress from "./work-order-address.vue";
import GzWoSignature from "./work-order-signature.vue";
export default {
components: {
GzWoState,
GzWoAddress,
GzWoSignature
GzQuoteState,
GzWoAddress
},
data() {
return {
@@ -543,7 +497,7 @@ Example workorder
wiki: null,
customFields: null,
tags: [],
workOrderId: 10,
quoteId: 10,
techNotes: "technotes",
workorderItemStatusId: null,
workorderItemPriorityId: null,
@@ -565,7 +519,7 @@ Example workorder
stopDate: null,
serviceRateId: null,
isDirty: false,
workOrderItemId: 21
quoteItemId: 21
},
{
id: 42,
@@ -576,7 +530,7 @@ Example workorder
stopDate: null,
serviceRateId: null,
isDirty: false,
workOrderItemId: 21
quoteItemId: 21
}
],
tasks: [],
@@ -591,7 +545,7 @@ Example workorder
wiki: null,
customFields: null,
tags: [],
workOrderId: 10,
quoteId: 10,
techNotes: "technotes",
workorderItemStatusId: null,
workorderItemPriorityId: null,
@@ -613,7 +567,7 @@ Example workorder
stopDate: null,
serviceRateId: null,
isDirty: false,
workOrderItemId: 22
quoteItemId: 22
},
{
id: 44,
@@ -624,7 +578,7 @@ Example workorder
stopDate: null,
serviceRateId: null,
isDirty: false,
workOrderItemId: 22
quoteItemId: 22
}
],
tasks: [],
@@ -637,7 +591,7 @@ Example workorder
{
id: 37,
concurrency: 7728489,
workOrderId: 10,
quoteId: 10,
workOrderStatusId: 2,
created: "2021-05-29T21:30:31.421011Z",
userId: 37,
@@ -647,7 +601,7 @@ Example workorder
{
id: 38,
concurrency: 7728489,
workOrderId: 10,
quoteId: 10,
workOrderStatusId: 3,
created: "2021-05-29T22:25:31.421011Z",
userId: 10,
@@ -657,7 +611,7 @@ Example workorder
{
id: 39,
concurrency: 7728489,
workOrderId: 10,
quoteId: 10,
workOrderStatusId: 1,
created: "2021-05-29T22:30:31.421011Z",
userId: 31,
@@ -667,7 +621,7 @@ Example workorder
{
id: 40,
concurrency: 7728489,
workOrderId: 10,
quoteId: 10,
workOrderStatusId: 3,
created: "2021-05-29T23:25:31.421011Z",
userId: 2,
@@ -677,7 +631,7 @@ Example workorder
{
id: 41,
concurrency: 7728489,
workOrderId: 10,
quoteId: 10,
workOrderStatusId: 9,
created: "2021-04-06T00:10:44.636Z",
userId: 1,

View File

@@ -0,0 +1,798 @@
<template>
<div v-if="value != null" class="mt-8">
<v-row>
<v-col cols="12">
<v-menu offset-y max-width="600px">
<template v-slot:activator="{ on, attrs }">
<div class="text-h6">
<v-icon large :color="hasData ? 'primary' : null" class="mr-2"
>$ayiMoneyBillWave</v-icon
>
{{ $ay.t("WorkOrderItemExpenseList") }}
<v-btn v-if="!parentDeleted" large icon v-bind="attrs" v-on="on">
<v-icon small color="primary">$ayiEllipsisV</v-icon>
</v-btn>
</div>
</template>
<v-list>
<v-list-item v-if="canAdd" @click="newItem">
<v-list-item-icon>
<v-icon>$ayiPlus</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ $ay.t("New") }}</v-list-item-title>
</v-list-item>
<v-list-item v-if="canDelete && isDeleted" @click="unDeleteItem">
<v-list-item-icon>
<v-icon>$ayiTrashRestoreAlt</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ $ay.t("Undelete") }}</v-list-item-title>
</v-list-item>
<v-list-item v-if="canDelete && !isDeleted" @click="deleteItem">
<v-list-item-icon>
<v-icon>$ayiTrashAlt</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ $ay.t("SoftDelete") }}</v-list-item-title>
</v-list-item>
<v-list-item v-if="canDeleteAll" @click="deleteAllItem">
<v-list-item-icon>
<v-icon>$ayiDumpster</v-icon>
</v-list-item-icon>
<v-list-item-title>{{
$ay.t("SoftDeleteAll")
}}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-col>
<template v-if="hasData">
<!-- ############################################################### -->
<v-col cols="12" class="mb-10">
<v-data-table
:headers="headerList"
:items="itemList"
item-key="index"
v-model="selectedRow"
class="elevation-1"
disable-pagination
disable-filtering
disable-sort
hide-default-footer
data-cy="expensesTable"
dense
:item-class="itemRowClasses"
@click:row="handleRowClick"
:show-select="$vuetify.breakpoint.xs"
single-select
>
<template v-slot:[`item.reimburseUser`]="{ item }">
<v-simple-checkbox
v-model="item.reimburseUser"
disabled
></v-simple-checkbox>
</template>
<template v-slot:[`item.chargeToCustomer`]="{ item }">
<v-simple-checkbox
v-model="item.chargeToCustomer"
disabled
></v-simple-checkbox>
</template>
</v-data-table>
</v-col>
</template>
<template v-if="hasData && hasSelection">
<div ref="expensetopform"></div>
<v-btn
v-if="canDelete && isDeleted"
large
@click="unDeleteItem"
color="primary"
>{{ $ay.t("Undelete")
}}<v-icon right large>$ayiTrashRestoreAlt</v-icon></v-btn
>
<v-col
v-if="form().showMe(this, 'WorkOrderItemExpenseName')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<v-text-field
v-model="
value.items[activeWoItemIndex].expenses[activeItemIndex].name
"
:readonly="formState.readOnly"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemExpenseName')"
:ref="
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].name`
"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].name`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].name`
)
"
></v-text-field>
</v-col>
<v-col
v-if="form().showMe(this, 'WorkOrderItemExpenseTotalCost')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-currency
v-model="
value.items[activeWoItemIndex].expenses[activeItemIndex].totalCost
"
:readonly="formState.readOnly || isDeleted"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemExpenseTotalCost')"
:ref="
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].totalCost`
"
data-cy="expenseTotalCost"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].totalCost`
)
"
:rules="[
form().decimalValid(
this,
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].totalCost`
),
form().required(
this,
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].totalCost`
)
]"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].totalCost`
)
"
></gz-currency>
</v-col>
<v-col
v-if="
form().showMe(this, 'WorkOrderItemExpenseChargeAmount') &&
!value.userIsRestrictedType
"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-currency
v-model="
value.items[activeWoItemIndex].expenses[activeItemIndex]
.chargeAmount
"
:readonly="formState.readOnly || isDeleted"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemExpenseChargeAmount')"
:ref="
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].chargeAmount`
"
data-cy="expenseChargeAmount"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].chargeAmount`
)
"
:rules="[
form().decimalValid(
this,
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].chargeAmount`
),
form().required(
this,
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].chargeAmount`
)
]"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].chargeAmount`
)
"
></gz-currency>
</v-col>
<v-col
v-if="
form().showMe(this, 'WorkOrderItemExpenseChargeToCustomer') &&
!value.userIsRestrictedType
"
cols="12"
sm="6"
lg="4"
xl="3"
>
<v-checkbox
v-model="
value.items[activeWoItemIndex].expenses[activeItemIndex]
.chargeToCustomer
"
:readonly="formState.readOnly"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemExpenseChargeToCustomer')"
:ref="
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].chargeToCustomer`
"
data-cy="chargeToCustomer"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].chargeToCustomer`
)
"
@change="
fieldValueChanged(
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].chargeToCustomer`
)
"
></v-checkbox>
</v-col>
<v-col
v-if="form().showMe(this, 'WorkOrderItemExpenseTaxPaid')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-currency
v-model="
value.items[activeWoItemIndex].expenses[activeItemIndex].taxPaid
"
:readonly="formState.readOnly || isDeleted"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemExpenseTaxPaid')"
:ref="
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].taxPaid`
"
data-cy="expenseTaxPaid"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].taxPaid`
)
"
:rules="[
form().decimalValid(
this,
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].taxPaid`
),
form().required(
this,
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].taxPaid`
)
]"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].taxPaid`
)
"
></gz-currency>
</v-col>
<v-col
v-if="
form().showMe(this, 'WorkOrderItemExpenseChargeTaxCodeID') &&
!value.userIsRestrictedType
"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-pick-list
:aya-type="$ay.ayt().TaxCode"
show-edit-icon
v-model="
value.items[activeWoItemIndex].expenses[activeItemIndex]
.chargeTaxCodeId
"
:readonly="formState.readOnly || isDeleted"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemExpenseChargeTaxCodeID')"
:ref="
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].chargeTaxCodeId`
"
data-cy="expenseChargeTaxCode"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].chargeTaxCodeId`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].chargeTaxCodeId`
)
"
@update:name="taxCodeChange"
></gz-pick-list>
</v-col>
<v-col
v-if="
form().showMe(this, 'WorkOrderItemExpenseReimburseUser') &&
!value.userIsRestrictedType
"
cols="12"
sm="6"
lg="4"
xl="3"
>
<v-checkbox
v-model="
value.items[activeWoItemIndex].expenses[activeItemIndex]
.reimburseUser
"
:readonly="formState.readOnly"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemExpenseReimburseUser')"
:ref="
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].reimburseUser`
"
data-cy="expenseReimburseUser"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].reimburseUser`
)
"
@change="
fieldValueChanged(
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].reimburseUser`
)
"
></v-checkbox>
</v-col>
<v-col
v-if="form().showMe(this, 'WorkOrderItemExpenseUserID')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-pick-list
:aya-type="$ay.ayt().User"
variant="tech"
:show-edit-icon="!value.userIsRestrictedType"
v-model="
value.items[activeWoItemIndex].expenses[activeItemIndex].userId
"
:readonly="
formState.readOnly || isDeleted || value.userIsRestrictedType
"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemExpenseUserID')"
:ref="
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].userId`
"
data-cy="expenseUser"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].userId`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].userId`
)
"
@update:name="userChange"
></gz-pick-list>
</v-col>
<v-col
v-if="form().showMe(this, 'WorkOrderItemExpenseDescription')"
cols="12"
>
<v-textarea
v-model="
value.items[activeWoItemIndex].expenses[activeItemIndex]
.description
"
:readonly="formState.readOnly"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemExpenseDescription')"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].description`
)
"
:ref="
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].description`
"
data-cy="expenseDescription"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].expenses[${activeItemIndex}].description`
)
"
auto-grow
></v-textarea>
</v-col>
</template>
</v-row>
</div>
</template>
<script>
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/* XXXeslint-disable */
////////////////////////////////////////////////////////////////////////////////////////////////////////////
export default {
created() {
this.setDefaultView();
},
data() {
return {
activeItemIndex: null,
selectedRow: []
};
},
props: {
value: {
default: null,
type: Object
},
pvm: {
default: null,
type: Object
},
activeWoItemIndex: {
default: null,
type: Number
},
gotoIndex: {
default: null,
type: Number
}
},
watch: {
activeWoItemIndex(val, oldVal) {
if (val != oldVal) {
this.setDefaultView();
}
},
gotoIndex(val, oldVal) {
if (val != oldVal) {
let gotoIndex = val;
if (val < 0) {
//it's a create request
gotoIndex = this.newItem();
}
this.selectedRow = [{ index: gotoIndex }];
this.activeItemIndex = gotoIndex;
this.$nextTick(() => {
const el = this.$refs.expensetopform;
if (el) {
el.scrollIntoView({ behavior: "smooth" });
}
});
}
}
},
methods: {
userChange(newName) {
this.value.items[this.activeWoItemIndex].expenses[
this.activeItemIndex
].userViz = newName;
},
taxCodeChange(newName) {
this.value.items[this.activeWoItemIndex].expenses[
this.activeItemIndex
].taxCodeViz = newName;
},
newItem() {
let newIndex = this.value.items[this.activeWoItemIndex].expenses.length;
//#################################### IMPORTANT ##################################################
//NOTE: default values are critical and must match server validation ExpenseValidateAsync for restricted users
//so that they are in agreement otherwise restricted users will never be able to create new records
this.value.items[this.activeWoItemIndex].expenses.push({
id: 0,
concurrency: 0,
description: null,
name: null,
totalCost: 0,
chargeAmount: 0,
taxPaid: 0,
chargeTaxCodeId: null,
taxCodeViz: null,
reimburseUser: false,
userId: this.$store.getters.isScheduleableUser
? this.$store.state.userId
: null,
userViz: this.$store.getters.isScheduleableUser
? this.$store.state.userName
: null,
chargeToCustomer: false,
isDirty: true,
quoteItemId: this.value.items[this.activeWoItemIndex].id,
uid: Date.now() //used for error tracking / display
});
this.$emit("change");
this.selectedRow = [{ index: newIndex }];
this.activeItemIndex = newIndex;
return newIndex; //for create new on goto
},
unDeleteItem() {
this.value.items[this.activeWoItemIndex].expenses[
this.activeItemIndex
].deleted = false;
this.setDefaultView();
},
deleteItem() {
this.value.items[this.activeWoItemIndex].expenses[
this.activeItemIndex
].deleted = true;
this.setDefaultView();
this.$emit("change");
},
deleteAllItem() {
this.value.items[this.activeWoItemIndex].expenses.forEach(
z => (z.deleted = true)
);
this.setDefaultView();
this.$emit("change");
},
setDefaultView: function() {
//if only one record left then display it otherwise just let the datatable show what the user can click on
if (
this.hasData &&
this.value.items[this.activeWoItemIndex].expenses.length == 1
) {
this.selectedRow = [{ index: 0 }];
this.activeItemIndex = 0;
} else {
this.selectedRow = [];
this.activeItemIndex = null; //select nothing in essence resetting a child selects and this one too clearing form
}
},
handleRowClick: function(item) {
this.activeItemIndex = item.index;
this.selectedRow = [{ index: item.index }];
},
form() {
return window.$gz.form;
},
fieldValueChanged(ref) {
if (!this.formState.loading && !this.formState.readonly) {
//flag this record dirty so it gets picked up by save
this.value.items[this.activeWoItemIndex].expenses[
this.activeItemIndex
].isDirty = true;
window.$gz.form.fieldValueChanged(this.pvm, ref);
// //set viz if applicable
// if(ref==""){
// let selectedPartWarehouse = vm.$refs.partWarehouseId.getFullSelectionValue();
// }
}
},
itemRowClasses: function(item) {
let ret = "";
const isDeleted =
this.value.items[this.activeWoItemIndex].expenses[item.index]
.deleted === true;
const hasError = this.form().childRowHasError(
this,
`Items[${this.activeWoItemIndex}].Expenses[${item.index}].`
);
if (isDeleted) {
ret += this.form().tableRowDeletedClass();
}
if (hasError) {
ret += this.form().tableRowErrorClass();
}
return ret;
}
},
computed: {
isDeleted: function() {
if (
this.value.items[this.activeWoItemIndex].expenses[
this.activeItemIndex
] == null
) {
this.setDefaultView();
return true;
}
return (
this.value.items[this.activeWoItemIndex].expenses[this.activeItemIndex]
.deleted === true
);
},
parentDeleted: function() {
return this.value.items[this.activeWoItemIndex].deleted === true;
},
headerList: function() {
/*
If the column is a text, left-align it
If the column is a number or number + unit, (or date) right-align it (like excel)
*/
let headers = [];
if (this.form().showMe(this, "WorkOrderItemExpenseName")) {
headers.push({
text: this.$ay.t("WorkOrderItemExpenseName"),
align: "start",
value: "name"
});
}
if (this.form().showMe(this, "WorkOrderItemExpenseTotalCost")) {
headers.push({
text: this.$ay.t("WorkOrderItemExpenseTotalCost"),
align: "right",
value: "totalCost"
});
}
if (this.form().showMe(this, "WorkOrderItemExpenseChargeAmount")) {
headers.push({
text: this.$ay.t("WorkOrderItemExpenseChargeAmount"),
align: "right",
value: "chargeAmount"
});
}
if (this.form().showMe(this, "WorkOrderItemExpenseChargeToCustomer")) {
headers.push({
text: this.$ay.t("WorkOrderItemExpenseChargeToCustomer"),
align: "center",
value: "chargeToCustomer"
});
}
if (this.form().showMe(this, "WorkOrderItemExpenseTaxPaid")) {
headers.push({
text: this.$ay.t("WorkOrderItemExpenseTaxPaid"),
align: "right",
value: "taxPaid"
});
}
if (this.form().showMe(this, "WorkOrderItemExpenseChargeTaxCodeID")) {
headers.push({
text: this.$ay.t("Tax"),
align: "left",
value: "taxCodeViz"
});
}
if (this.form().showMe(this, "ExpenseTaxAViz")) {
headers.push({
text: this.$ay.t("TaxAAmt"),
align: "right",
value: "taxAViz"
});
}
if (this.form().showMe(this, "ExpenseTaxBViz")) {
headers.push({
text: this.$ay.t("TaxBAmt"),
align: "right",
value: "taxBViz"
});
}
if (this.form().showMe(this, "ExpenseLineTotalViz")) {
headers.push({
text: this.$ay.t("LineTotal"),
align: "right",
value: "lineTotalViz"
});
}
if (this.form().showMe(this, "WorkOrderItemExpenseReimburseUser")) {
headers.push({
text: this.$ay.t("WorkOrderItemExpenseReimburseUser"),
align: "center",
value: "reimburseUser"
});
}
if (this.form().showMe(this, "WorkOrderItemExpenseUserID")) {
headers.push({
text: this.$ay.t("WorkOrderItemExpenseUserID"),
align: "left",
value: "userViz"
});
}
return headers;
},
itemList: function() {
return this.value.items[this.activeWoItemIndex].expenses.map((x, i) => {
return {
index: i,
id: x.id,
name: x.name,
totalCost: window.$gz.locale.currencyLocalized(
x.totalCost,
this.pvm.languageName,
this.pvm.currencyName
),
chargeAmount: window.$gz.locale.currencyLocalized(
x.chargeAmount,
this.pvm.languageName,
this.pvm.currencyName
),
chargeToCustomer: x.chargeToCustomer,
taxPaid: window.$gz.locale.currencyLocalized(
x.taxPaid,
this.pvm.languageName,
this.pvm.currencyName
),
taxCodeViz: x.taxCodeViz,
taxAViz: window.$gz.locale.currencyLocalized(
x.taxAViz,
this.pvm.languageName,
this.pvm.currencyName
),
taxBViz: window.$gz.locale.currencyLocalized(
x.taxBViz,
this.pvm.languageName,
this.pvm.currencyName
),
lineTotalViz: window.$gz.locale.currencyLocalized(
x.lineTotalViz,
this.pvm.languageName,
this.pvm.currencyName
),
reimburseUser: x.reimburseUser,
userViz: x.userViz
};
});
},
formState: function() {
return this.pvm.formState;
},
formCustomTemplateKey: function() {
return this.pvm.formCustomTemplateKey;
},
hasData: function() {
return this.value.items[this.activeWoItemIndex].expenses.length > 0;
},
hasSelection: function() {
return this.activeItemIndex != null;
},
canAdd: function() {
return this.pvm.rights.change;
},
canDelete: function() {
return this.activeItemIndex != null && this.pvm.rights.change;
},
canDeleteAll: function() {
return this.pvm.rights.change && !this.value.userIsRestrictedType;
}
}
};
</script>

View File

@@ -0,0 +1,997 @@
<template>
<div v-if="value != null" class="mt-8">
<v-row>
<v-col cols="12">
<v-menu offset-y max-width="600px">
<template v-slot:activator="{ on, attrs }">
<div class="text-h6">
<v-icon large :color="hasData ? 'primary' : null" class="mr-2"
>$ayiHammer</v-icon
>
{{ $ay.t("WorkOrderItemLaborList") }}
<v-btn v-if="!parentDeleted" large icon v-bind="attrs" v-on="on">
<v-icon small color="primary">$ayiEllipsisV</v-icon>
</v-btn>
</div>
</template>
<v-list>
<v-list-item v-if="canAdd" @click="newItem">
<v-list-item-icon>
<v-icon>$ayiPlus</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ $ay.t("New") }}</v-list-item-title>
</v-list-item>
<v-list-item
v-if="hasSelection && !formState.readOnly"
@click="appendTasks"
>
<v-list-item-icon>
<v-icon>$ayiTasks</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ $ay.t("AppendTasks") }}</v-list-item-title>
</v-list-item>
<v-list-item v-if="canDelete && isDeleted" @click="unDeleteItem">
<v-list-item-icon>
<v-icon>$ayiTrashRestoreAlt</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ $ay.t("Undelete") }}</v-list-item-title>
</v-list-item>
<v-list-item v-if="canDelete && !isDeleted" @click="deleteItem">
<v-list-item-icon>
<v-icon>$ayiTrashAlt</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ $ay.t("SoftDelete") }}</v-list-item-title>
</v-list-item>
<v-list-item v-if="canDeleteAll" @click="deleteAllItem">
<v-list-item-icon>
<v-icon>$ayiDumpster</v-icon>
</v-list-item-icon>
<v-list-item-title>{{
$ay.t("SoftDeleteAll")
}}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-col>
<template v-if="hasData">
<!-- ############################################################### -->
<v-col cols="12" class="mb-10">
<v-data-table
:headers="headerList"
:items="itemList"
item-key="index"
v-model="selectedRow"
class="elevation-1"
disable-pagination
disable-filtering
disable-sort
hide-default-footer
data-cy="laborsTable"
dense
:item-class="itemRowClasses"
@click:row="handleRowClick"
:show-select="$vuetify.breakpoint.xs"
single-select
>
</v-data-table>
</v-col>
</template>
<template v-if="hasData && hasSelection">
<div ref="labortopform"></div>
<v-btn
v-if="canDelete && isDeleted"
large
@click="unDeleteItem"
color="primary"
>{{ $ay.t("Undelete")
}}<v-icon right large>$ayiTrashRestoreAlt</v-icon></v-btn
>
<v-col
v-if="form().showMe(this, 'WorkOrderItemLaborServiceStartDate')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-date-time-picker
:label="$ay.t('WorkOrderItemLaborServiceStartDate')"
v-model="
value.items[activeWoItemIndex].labors[activeItemIndex]
.serviceStartDate
"
:readonly="formState.readOnly"
:disabled="isDeleted"
:ref="
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].serviceStartDate`
"
data-cy="serviceStartDate"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].serviceStartDate`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].serviceStartDate`
)
"
></gz-date-time-picker>
</v-col>
<v-col
v-if="form().showMe(this, 'WorkOrderItemLaborServiceStopDate')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-date-time-picker
:label="$ay.t('WorkOrderItemLaborServiceStopDate')"
v-model="
value.items[activeWoItemIndex].labors[activeItemIndex]
.serviceStopDate
"
:readonly="formState.readOnly"
:disabled="isDeleted"
:ref="
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].serviceStopDate`
"
data-cy="serviceStopDate"
:rules="[
form().datePrecedence(
this,
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].serviceStartDate`,
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].serviceStopDate`
)
]"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].serviceStopDate`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].serviceStopDate`
)
"
></gz-date-time-picker>
</v-col>
<v-col
v-if="form().showMe(this, 'WorkOrderItemLaborServiceRateQuantity')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-decimal
v-model="
value.items[activeWoItemIndex].labors[activeItemIndex]
.serviceRateQuantity
"
:readonly="formState.readOnly || isDeleted"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemLaborServiceRateQuantity')"
:ref="
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].serviceRateQuantity`
"
data-cy="laborServiceRateQuantity"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].serviceRateQuantity`
)
"
:rules="[
form().decimalValid(
this,
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].serviceRateQuantity`
),
form().required(
this,
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].serviceRateQuantity`
)
]"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].serviceRateQuantity`
)
"
></gz-decimal>
</v-col>
<v-col
v-if="form().showMe(this, 'WorkOrderItemLaborServiceRateID')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-pick-list
:aya-type="$ay.ayt().ServiceRate"
:variant="'contractid:' + value.contractId"
:show-edit-icon="!value.userIsRestrictedType"
v-model="
value.items[activeWoItemIndex].labors[activeItemIndex]
.serviceRateId
"
:readonly="formState.readOnly || isDeleted"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemLaborServiceRateID')"
:ref="
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].serviceRateId`
"
data-cy="labors.serviceRateId"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].serviceRateId`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].serviceRateId`
)
"
@update:name="rateChange"
></gz-pick-list>
</v-col>
<v-col
v-if="form().showMe(this, 'WorkOrderItemLaborUserID')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-pick-list
:aya-type="$ay.ayt().User"
variant="tech"
:show-edit-icon="!value.userIsRestrictedType"
v-model="
value.items[activeWoItemIndex].labors[activeItemIndex].userId
"
:readonly="
formState.readOnly || isDeleted || value.userIsRestrictedType
"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemLaborUserID')"
:ref="
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].userId`
"
data-cy="labors.userid"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].userId`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].userId`
)
"
@update:name="userChange"
></gz-pick-list>
</v-col>
<v-col
v-if="form().showMe(this, 'WorkOrderItemLaborNoChargeQuantity')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-decimal
v-model="
value.items[activeWoItemIndex].labors[activeItemIndex]
.noChargeQuantity
"
:readonly="formState.readOnly || isDeleted"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemLaborNoChargeQuantity')"
:ref="
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].noChargeQuantity`
"
data-cy="laborNoChargeQuantity"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].noChargeQuantity`
)
"
:rules="[
form().decimalValid(
this,
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].noChargeQuantity`
),
form().required(
this,
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].noChargeQuantity`
)
]"
@input="
fieldValueChanged(`Items[${activeWoItemIndex}].labors[
${activeItemIndex}
].noChargeQuantity`)
"
></gz-decimal>
</v-col>
<v-col
v-if="
form().showMe(this, 'WorkOrderItemLaborTaxRateSaleID') &&
!value.userIsRestrictedType
"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-pick-list
:aya-type="$ay.ayt().TaxCode"
show-edit-icon
v-model="
value.items[activeWoItemIndex].labors[activeItemIndex]
.taxCodeSaleId
"
:readonly="formState.readOnly || isDeleted"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemLaborTaxRateSaleID')"
:ref="
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].taxCodeSaleId`
"
data-cy="laborTaxCodeSaleId"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].taxCodeSaleId`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].taxCodeSaleId`
)
"
@update:name="taxCodeChange"
></gz-pick-list>
</v-col>
<v-col
v-if="
form().showMe(this, 'LaborPriceOverride') &&
!value.userIsRestrictedType
"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-currency
v-model="
value.items[activeWoItemIndex].labors[activeItemIndex]
.priceOverride
"
can-clear
:readonly="formState.readOnly || isDeleted"
:disabled="isDeleted"
:label="$ay.t('PriceOverride')"
:ref="
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].priceOverride`
"
data-cy="laborpriceoverride"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].priceOverride`
)
"
:rules="[
form().decimalValid(
this,
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].priceOverride`
)
]"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].priceOverride`
)
"
></gz-currency>
</v-col>
<v-col
v-if="form().showMe(this, 'WorkOrderItemLaborServiceDetails')"
cols="12"
>
<v-textarea
v-model="
value.items[activeWoItemIndex].labors[activeItemIndex]
.serviceDetails
"
:readonly="formState.readOnly"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemLaborServiceDetails')"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].serviceDetails`
)
"
:ref="
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].serviceDetails`
"
data-cy="laborserviceDetails"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].labors[${activeItemIndex}].serviceDetails`
)
"
auto-grow
></v-textarea>
</v-col>
</template>
</v-row>
</div>
</template>
<script>
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/* XXXeslint-disable */
////////////////////////////////////////////////////////////////////////////////////////////////////////////
export default {
created() {
this.setDefaultView();
},
data() {
return {
activeItemIndex: null,
selectedRow: []
};
},
props: {
value: {
default: null,
type: Object
},
pvm: {
default: null,
type: Object
},
activeWoItemIndex: {
default: null,
type: Number
},
gotoIndex: {
default: null,
type: Number
}
},
watch: {
activeWoItemIndex(val, oldVal) {
if (val != oldVal) {
this.setDefaultView();
}
},
gotoIndex(val, oldVal) {
if (val != oldVal) {
let gotoIndex = val;
if (val < 0) {
//it's a create request
gotoIndex = this.newItem();
}
this.selectedRow = [{ index: gotoIndex }];
this.activeItemIndex = gotoIndex;
this.$nextTick(() => {
const el = this.$refs.labortopform;
if (el) {
el.scrollIntoView({ behavior: "smooth" });
}
});
}
}
},
methods: {
appendTasks() {
const tasks = this.value.items[this.activeWoItemIndex].tasks;
if (tasks.length == 0) {
return;
}
const l = this.value.items[this.activeWoItemIndex].labors[
this.activeItemIndex
];
let at = "";
tasks.forEach(z => {
at += `${z.task} - ${z.statusViz}\n`;
});
l.serviceDetails += `\n${at}`;
},
userChange(newName) {
this.value.items[this.activeWoItemIndex].labors[
this.activeItemIndex
].userViz = newName;
},
rateChange(newName) {
this.value.items[this.activeWoItemIndex].labors[
this.activeItemIndex
].serviceRateViz = newName;
},
taxCodeChange(newName) {
this.value.items[this.activeWoItemIndex].labors[
this.activeItemIndex
].taxCodeViz = newName;
},
newItem() {
let newIndex = this.value.items[this.activeWoItemIndex].labors.length;
this.value.items[this.activeWoItemIndex].labors.push({
id: 0,
concurrency: 0,
userId: this.$store.getters.isScheduleableUser
? this.$store.state.userId
: null,
serviceStartDate: null,
serviceStopDate: null,
serviceRateId: null,
serviceDetails: null,
serviceRateQuantity: 0,
noChargeQuantity: 0,
taxCodeSaleId: null,
price: 0,
priceOverride: null,
isDirty: true,
quoteItemId: this.value.items[this.activeWoItemIndex].id,
uid: Date.now(),
userViz: this.$store.getters.isScheduleableUser
? this.$store.state.userName
: null,
serviceRateViz: null,
taxCodeViz: null
});
this.$emit("change");
this.selectedRow = [{ index: newIndex }];
this.activeItemIndex = newIndex;
return newIndex; //for create new on goto
},
unDeleteItem() {
this.value.items[this.activeWoItemIndex].labors[
this.activeItemIndex
].deleted = false;
this.setDefaultView();
},
deleteItem() {
this.value.items[this.activeWoItemIndex].labors[
this.activeItemIndex
].deleted = true;
this.setDefaultView();
this.$emit("change");
},
deleteAllItem() {
this.value.items[this.activeWoItemIndex].labors.forEach(
z => (z.deleted = true)
);
this.setDefaultView();
this.$emit("change");
},
setDefaultView: function() {
//if only one record left then display it otherwise just let the datatable show what the user can click on
if (this.value.items[this.activeWoItemIndex].labors.length == 1) {
this.selectedRow = [{ index: 0 }];
this.activeItemIndex = 0;
} else {
this.selectedRow = [];
this.activeItemIndex = null; //select nothing in essence resetting a child selects and this one too clearing form
}
},
handleRowClick: function(item) {
this.activeItemIndex = item.index;
this.selectedRow = [{ index: item.index }];
},
form() {
return window.$gz.form;
},
fieldValueChanged(ref) {
if (!this.formState.loading && !this.formState.readonly) {
//flag this record dirty so it gets picked up by save
this.value.items[this.activeWoItemIndex].labors[
this.activeItemIndex
].isDirty = true;
window.$gz.form.fieldValueChanged(this.pvm, ref);
//------- SPECIAL HANDLING OF CHANGES -----------
const isNew =
this.value.items[this.activeWoItemIndex].labors[this.activeItemIndex]
.id == 0;
//Auto calculate dates / quantities / global defaults
const dStart = this.value.items[this.activeWoItemIndex].labors[
this.activeItemIndex
].serviceStartDate;
const dStop = this.value.items[this.activeWoItemIndex].labors[
this.activeItemIndex
].serviceStopDate;
if (ref.includes("serviceStartDate") && dStart != null) {
this.handleStartDateChange(isNew, dStart, dStop);
}
if (ref.includes("serviceStopDate") && dStop != null) {
this.handleStopDateChange(isNew, dStart, dStop);
}
//------------------------------------------------------
}
},
handleStartDateChange: function(isNew, dStart, dStop) {
const globalMinutes =
window.$gz.store.state.globalSettings.workLaborScheduleDefaultMinutes;
if (isNew && dStop == null) {
if (globalMinutes != 0) {
//set stop date based on start date and global minutes
this.value.items[this.activeWoItemIndex].labors[
this.activeItemIndex
].serviceStopDate = window.$gz.locale.addMinutesToUTC8601String(
dStart,
globalMinutes
);
this.value.items[this.activeWoItemIndex].labors[
this.activeItemIndex
].serviceRateQuantity = globalMinutes;
}
} else {
//Existing record or both dates filled, just update quantity
if (dStop != null) {
this.value.items[this.activeWoItemIndex].labors[
this.activeItemIndex
].serviceRateQuantity = window.$gz.locale.diffHoursFromUTC8601String(
dStart,
dStop
);
}
}
},
handleStopDateChange: function(isNew, dStart, dStop) {
const globalMinutes =
window.$gz.store.state.globalSettings.workLaborScheduleDefaultMinutes;
if (isNew && dStart == null) {
if (globalMinutes != 0) {
//set start date based on stop date and global minutes
this.value.items[this.activeWoItemIndex].labors[
this.activeItemIndex
].serviceStartDate = window.$gz.locale.addMinutesToUTC8601String(
dStop,
0 - globalMinutes
);
this.value.items[this.activeWoItemIndex].labors[
this.activeItemIndex
].serviceRateQuantity = globalMinutes;
}
} else {
//Existing record or both dates filled, just update quantity
if (dStart != null) {
this.value.items[this.activeWoItemIndex].labors[
this.activeItemIndex
].serviceRateQuantity = window.$gz.locale.diffHoursFromUTC8601String(
dStart,
dStop
);
}
}
},
itemRowClasses: function(item) {
let ret = "";
const isDeleted =
this.value.items[this.activeWoItemIndex].labors[item.index].deleted ===
true;
const hasError = this.form().childRowHasError(
this,
`Items[${this.activeWoItemIndex}].Labors[${item.index}].`
);
if (isDeleted) {
ret += this.form().tableRowDeletedClass();
}
if (hasError) {
ret += this.form().tableRowErrorClass();
}
return ret;
}
//---
},
computed: {
isDeleted: function() {
if (
this.value.items[this.activeWoItemIndex].labors[this.activeItemIndex] ==
null
) {
this.setDefaultView();
return true;
}
return (
this.value.items[this.activeWoItemIndex].labors[this.activeItemIndex]
.deleted === true
);
},
parentDeleted: function() {
return this.value.items[this.activeWoItemIndex].deleted === true;
},
headerList: function() {
/*
If the column is a text, left-align it
If the column is a number or number + unit, (or date) right-align it (like excel)
*/
let headers = [];
if (this.form().showMe(this, "WorkOrderItemLaborServiceStartDate")) {
headers.push({
text: this.$ay.t("WorkOrderItemLaborServiceStartDate"),
align: "right",
value: "serviceStartDate"
});
}
if (this.form().showMe(this, "WorkOrderItemLaborServiceStopDate")) {
headers.push({
text: this.$ay.t("WorkOrderItemLaborServiceStopDate"),
align: "right",
value: "serviceStopDate"
});
}
if (this.form().showMe(this, "WorkOrderItemLaborServiceRateQuantity")) {
headers.push({
text: this.$ay.t("WorkOrderItemLaborServiceRateQuantity"),
align: "right",
value: "serviceRateQuantity"
});
}
if (this.form().showMe(this, "WorkOrderItemLaborServiceRateID")) {
headers.push({
text: this.$ay.t("WorkOrderItemLaborServiceRateID"),
align: "left",
value: "serviceRateViz"
});
}
if (this.form().showMe(this, "WorkOrderItemLaborServiceDetails")) {
headers.push({
text: this.$ay.t("WorkOrderItemLaborServiceDetails"),
align: "left",
value: "serviceDetails"
});
}
if (this.form().showMe(this, "WorkOrderItemLaborUserID")) {
headers.push({
text: this.$ay.t("WorkOrderItemLaborUserID"),
align: "left",
value: "userViz"
});
}
if (this.form().showMe(this, "WorkOrderItemLaborNoChargeQuantity")) {
headers.push({
text: this.$ay.t("WorkOrderItemLaborNoChargeQuantity"),
align: "right",
value: "noChargeQuantity"
});
}
if (this.form().showMe(this, "LaborUnitOfMeasureViz")) {
headers.push({
text: this.$ay.t("UnitOfMeasure"),
align: "left",
value: "unitOfMeasureViz"
});
}
if (
this.form().showMe(this, "LaborCostViz") &&
this.value.userCanViewLaborOrTravelRateCosts &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("Cost"),
align: "right",
value: "costViz"
});
}
if (
this.form().showMe(this, "LaborListPriceViz") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("ListPrice"),
align: "right",
value: "listPriceViz"
});
}
if (
this.form().showMe(this, "LaborPriceOverride") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("PriceOverride"),
align: "right",
value: "priceOverride"
});
}
if (
this.form().showMe(this, "LaborPriceViz") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("Price"),
align: "right",
value: "priceViz"
});
}
if (
this.form().showMe(this, "WorkOrderItemLaborTaxRateSaleID") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("Tax"),
align: "left",
value: "taxCodeViz"
});
}
if (
this.form().showMe(this, "LaborNetViz") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("NetPrice"),
align: "right",
value: "netViz"
});
}
if (
this.form().showMe(this, "LaborTaxAViz") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("TaxAAmt"),
align: "right",
value: "taxAViz"
});
}
if (
this.form().showMe(this, "LaborTaxBViz") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("TaxBAmt"),
align: "right",
value: "taxBViz"
});
}
if (
this.form().showMe(this, "LaborLineTotalViz") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("LineTotal"),
align: "right",
value: "lineTotalViz"
});
}
return headers;
},
itemList: function() {
return this.value.items[this.activeWoItemIndex].labors.map((x, i) => {
return {
index: i,
id: x.id,
serviceStartDate: window.$gz.locale.utcDateToShortDateAndTimeLocalized(
x.serviceStartDate,
this.pvm.timeZoneName,
this.pvm.languageName,
this.pvm.hour12
),
serviceStopDate: window.$gz.locale.utcDateToShortDateAndTimeLocalized(
x.serviceStopDate,
this.pvm.timeZoneName,
this.pvm.languageName,
this.pvm.hour12
),
serviceRateQuantity: window.$gz.locale.decimalLocalized(
x.serviceRateQuantity,
this.pvm.languageName
),
serviceRateViz: x.serviceRateViz,
userViz: x.userViz,
noChargeQuantity: window.$gz.locale.decimalLocalized(
x.noChargeQuantity,
this.pvm.languageName
),
unitOfMeasureViz: x.unitOfMeasureViz,
costViz: window.$gz.locale.currencyLocalized(
x.costViz,
this.pvm.languageName,
this.pvm.currencyName
),
listPriceViz: window.$gz.locale.currencyLocalized(
x.listPriceViz,
this.pvm.languageName,
this.pvm.currencyName
),
priceViz: window.$gz.locale.currencyLocalized(
x.priceViz,
this.pvm.languageName,
this.pvm.currencyName
),
taxCodeViz: x.taxCodeViz,
priceOverride: window.$gz.locale.currencyLocalized(
x.priceOverride,
this.pvm.languageName,
this.pvm.currencyName
),
netViz: window.$gz.locale.currencyLocalized(
x.netViz,
this.pvm.languageName,
this.pvm.currencyName
),
taxAViz: window.$gz.locale.currencyLocalized(
x.taxAViz,
this.pvm.languageName,
this.pvm.currencyName
),
taxBViz: window.$gz.locale.currencyLocalized(
x.taxBViz,
this.pvm.languageName,
this.pvm.currencyName
),
lineTotalViz: window.$gz.locale.currencyLocalized(
x.lineTotalViz,
this.pvm.languageName,
this.pvm.currencyName
),
serviceDetails: window.$gz.util.truncateString(
x.serviceDetails,
this.pvm.maxTableNotesLength
)
};
});
},
formState: function() {
return this.pvm.formState;
},
formCustomTemplateKey: function() {
return this.pvm.formCustomTemplateKey;
},
hasData: function() {
return this.value.items[this.activeWoItemIndex].labors.length > 0;
},
hasSelection: function() {
return this.activeItemIndex != null;
},
canAdd: function() {
return this.pvm.rights.change;
},
canDelete: function() {
return this.activeItemIndex != null && this.pvm.rights.change;
},
canDeleteAll: function() {
return this.pvm.rights.change && !this.value.userIsRestrictedType;
}
//----
}
};
</script>

View File

@@ -0,0 +1,885 @@
<template>
<div v-if="value != null" class="mt-8">
<v-row>
<v-col cols="12">
<v-menu offset-y max-width="600px">
<template v-slot:activator="{ on, attrs }">
<div class="text-h6">
<v-icon large :color="hasData ? 'primary' : null" class="mr-2"
>$ayiPlug</v-icon
>
{{ $ay.t("WorkOrderItemLoanList") }}
<v-btn v-if="!parentDeleted" large icon v-bind="attrs" v-on="on">
<v-icon small color="primary">$ayiEllipsisV</v-icon>
</v-btn>
</div>
</template>
<v-list>
<v-list-item v-if="canAdd" @click="newItem">
<v-list-item-icon>
<v-icon>$ayiPlus</v-icon>
</v-list-item-icon>
<v-list-item-title>{{
$ay.t("New")
}}</v-list-item-title> </v-list-item
><v-list-item v-if="canDelete && isDeleted" @click="unDeleteItem">
<v-list-item-icon>
<v-icon>$ayiTrashRestoreAlt</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ $ay.t("Undelete") }}</v-list-item-title>
</v-list-item>
<v-list-item v-if="canDelete && !isDeleted" @click="deleteItem">
<v-list-item-icon>
<v-icon>$ayiTrashAlt</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ $ay.t("SoftDelete") }}</v-list-item-title>
</v-list-item>
<v-list-item v-if="canDeleteAll" @click="deleteAllItem">
<v-list-item-icon>
<v-icon>$ayiDumpster</v-icon>
</v-list-item-icon>
<v-list-item-title>{{
$ay.t("SoftDeleteAll")
}}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-col>
<template v-if="hasData">
<!-- ############################################################### -->
<v-col cols="12" class="mb-10">
<v-data-table
:headers="headerList"
:items="itemList"
item-key="index"
v-model="selectedRow"
class="elevation-1"
disable-pagination
disable-filtering
disable-sort
hide-default-footer
data-cy="loansTable"
dense
:item-class="itemRowClasses"
@click:row="handleRowClick"
:show-select="$vuetify.breakpoint.xs"
single-select
>
</v-data-table>
</v-col>
</template>
<template v-if="hasData && hasSelection">
<div ref="loantopform"></div>
<v-btn
v-if="canDelete && isDeleted"
large
@click="unDeleteItem"
color="primary"
>{{ $ay.t("Undelete")
}}<v-icon right large>$ayiTrashRestoreAlt</v-icon></v-btn
>
<v-col
v-if="form().showMe(this, 'WorkOrderItemLoanUnit')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-pick-list
:aya-type="$ay.ayt().LoanUnit"
:show-edit-icon="!value.userIsRestrictedType"
v-model="
value.items[activeWoItemIndex].loans[activeItemIndex].loanUnitId
"
:readonly="
formState.readOnly || isDeleted || value.userIsRestrictedType
"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemLoanUnit')"
:ref="
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].loanUnitId`
"
data-cy="loans.loanUnitId"
:rules="[
form().required(
this,
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].loanUnitId`
)
]"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].loanUnitId`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].loanUnitId`
)
"
@update:name="loanUnitChange"
></gz-pick-list>
</v-col>
<v-col
v-if="
form().showMe(this, 'WorkOrderItemLoanRate') &&
!value.userIsRestrictedType
"
cols="12"
sm="6"
lg="4"
xl="3"
>
<v-select
v-model="value.items[activeWoItemIndex].loans[activeItemIndex].rate"
:items="pvm.selectLists.loanUnitRateUnits"
item-text="name"
item-value="id"
:readonly="formState.readOnly || isDeleted"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemLoanRate')"
:ref="`Items[${activeWoItemIndex}].loans[${activeItemIndex}].rate`"
data-cy="loanUnitRateUnit"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].rate`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].rate`
)
"
@change="loanUnitRateUnitChange"
></v-select>
</v-col>
<v-col
v-if="
form().showMe(this, 'WorkOrderItemLoanQuantity') &&
!value.userIsRestrictedType
"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-decimal
v-model="
value.items[activeWoItemIndex].loans[activeItemIndex].quantity
"
:readonly="formState.readOnly || isDeleted"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemLoanQuantity')"
:ref="
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].quantity`
"
data-cy="loanQuantity"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].quantity`
)
"
:rules="[
form().decimalValid(
this,
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].quantity`
),
form().required(
this,
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].quantity`
)
]"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].quantity`
)
"
></gz-decimal>
</v-col>
<v-col
v-if="form().showMe(this, 'WorkOrderItemLoanOutDate')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-date-time-picker
:label="$ay.t('WorkOrderItemLoanOutDate')"
v-model="
value.items[activeWoItemIndex].loans[activeItemIndex].outDate
"
:readonly="formState.readOnly || value.userIsRestrictedType"
:disabled="isDeleted"
:ref="
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].outDate`
"
data-cy="loanUnitOutDate"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].outDate`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].outDate`
)
"
></gz-date-time-picker>
</v-col>
<v-col
v-if="form().showMe(this, 'WorkOrderItemLoanDueDate')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-date-time-picker
:label="$ay.t('WorkOrderItemLoanDueDate')"
v-model="
value.items[activeWoItemIndex].loans[activeItemIndex].dueDate
"
:readonly="formState.readOnly || value.userIsRestrictedType"
:disabled="isDeleted"
:ref="
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].dueDate`
"
data-cy="loanUnitDueDate"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].dueDate`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].dueDate`
)
"
></gz-date-time-picker>
</v-col>
<v-col
v-if="form().showMe(this, 'WorkOrderItemLoanReturnDate')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-date-time-picker
:label="$ay.t('WorkOrderItemLoanReturnDate')"
v-model="
value.items[activeWoItemIndex].loans[activeItemIndex].returnDate
"
:readonly="formState.readOnly || value.userIsRestrictedType"
:disabled="isDeleted"
:ref="
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].returnDate`
"
data-cy="loanUnitReturnDate"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].returnDate`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].returnDate`
)
"
></gz-date-time-picker>
</v-col>
<v-col
v-if="
form().showMe(this, 'WorkOrderItemLoanTaxCodeID') &&
!value.userIsRestrictedType
"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-pick-list
:aya-type="$ay.ayt().TaxCode"
show-edit-icon
v-model="
value.items[activeWoItemIndex].loans[activeItemIndex].taxCodeId
"
:readonly="formState.readOnly || isDeleted"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemLoanTaxCodeID')"
:ref="
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].taxCodeId`
"
data-cy="loanTaxCodeSaleId"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].taxCodeId`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].taxCodeId`
)
"
@update:name="taxCodeChange"
></gz-pick-list>
</v-col>
<v-col
v-if="
form().showMe(this, 'LoanPriceOverride') &&
!value.userIsRestrictedType
"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-currency
v-model="
value.items[activeWoItemIndex].loans[activeItemIndex]
.priceOverride
"
can-clear
:readonly="formState.readOnly || isDeleted"
:disabled="isDeleted"
:label="$ay.t('PriceOverride')"
:ref="
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].priceOverride`
"
data-cy="loanpriceoverride"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].priceOverride`
)
"
:rules="[
form().decimalValid(
this,
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].priceOverride`
)
]"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].priceOverride`
)
"
></gz-currency>
</v-col>
<v-col v-if="form().showMe(this, 'WorkOrderItemLoanNotes')" cols="12">
<v-textarea
v-model="
value.items[activeWoItemIndex].loans[activeItemIndex].notes
"
:readonly="formState.readOnly || value.userIsRestrictedType"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemLoanNotes')"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].notes`
)
"
:ref="`Items[${activeWoItemIndex}].loans[${activeItemIndex}].notes`"
data-cy="loanUnitNotes"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].loans[${activeItemIndex}].notes`
)
"
auto-grow
></v-textarea>
</v-col>
</template>
</v-row>
</div>
</template>
<script>
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/* XXXeslint-disable */
////////////////////////////////////////////////////////////////////////////////////////////////////////////
export default {
created() {
this.setDefaultView();
},
data() {
return {
activeItemIndex: null,
selectedRow: []
};
},
props: {
value: {
default: null,
type: Object
},
pvm: {
default: null,
type: Object
},
activeWoItemIndex: {
default: null,
type: Number
},
gotoIndex: {
default: null,
type: Number
}
},
watch: {
activeWoItemIndex(val, oldVal) {
if (val != oldVal) {
this.setDefaultView();
}
},
gotoIndex(val, oldVal) {
if (val != oldVal) {
let gotoIndex = val;
if (val < 0) {
//it's a create request
gotoIndex = this.newItem();
}
this.selectedRow = [{ index: gotoIndex }];
this.activeItemIndex = gotoIndex;
this.$nextTick(() => {
const el = this.$refs.loantopform;
if (el) {
el.scrollIntoView({ behavior: "smooth" });
}
});
}
}
},
methods: {
loanUnitRateUnitChange(newId) {
this.value.items[this.activeWoItemIndex].loans[
this.activeItemIndex
].unitOfMeasureViz = this.pvm.selectLists.loanUnitRateUnits.find(
s => s.id == newId
).name;
},
loanUnitChange(newName) {
this.value.items[this.activeWoItemIndex].loans[
this.activeItemIndex
].loanUnitViz = newName;
},
taxCodeChange(newName) {
this.value.items[this.activeWoItemIndex].loans[
this.activeItemIndex
].taxCodeViz = newName;
},
newItem() {
let newIndex = this.value.items[this.activeWoItemIndex].loans.length;
this.value.items[this.activeWoItemIndex].loans.push({
id: 0,
concurrency: 0,
notes: null,
outDate: null,
dueDate: null,
returnDate: null,
taxCodeId: null,
loanUnitId: 0, //zero to break rule on new
quantity: 1,
rate: 1,
cost: 0,
priceOverride: null,
isDirty: true,
quoteItemId: this.value.items[this.activeWoItemIndex].id,
uid: Date.now(),
loanUnitViz: null,
taxCodeViz: null,
unitOfMeasureViz: null
});
this.$emit("change");
this.selectedRow = [{ index: newIndex }];
this.activeItemIndex = newIndex;
//trigger rule breaking / validation
this.$nextTick(() => {
this.value.items[this.activeWoItemIndex].loans[
this.activeItemIndex
].loanUnitId = null;
this.fieldValueChanged(
`Items[${this.activeWoItemIndex}].loans[${this.activeItemIndex}].loanUnitId`
);
});
return newIndex; //for create new on goto
},
unDeleteItem() {
this.value.items[this.activeWoItemIndex].loans[
this.activeItemIndex
].deleted = false;
this.setDefaultView();
},
deleteItem() {
this.value.items[this.activeWoItemIndex].loans[
this.activeItemIndex
].deleted = true;
this.setDefaultView();
this.$emit("change");
},
deleteAllItem() {
this.value.items[this.activeWoItemIndex].loans.forEach(
z => (z.deleted = true)
);
this.setDefaultView();
this.$emit("change");
},
setDefaultView: function() {
//if only one record left then display it otherwise just let the datatable show what the user can click on
if (this.value.items[this.activeWoItemIndex].loans.length == 1) {
this.selectedRow = [{ index: 0 }];
this.activeItemIndex = 0;
} else {
this.selectedRow = [];
this.activeItemIndex = null; //select nothing in essence resetting a child selects and this one too clearing form
}
},
handleRowClick: function(item) {
this.activeItemIndex = item.index;
this.selectedRow = [{ index: item.index }];
},
form() {
return window.$gz.form;
},
fieldValueChanged(ref) {
if (!this.formState.loading && !this.formState.readonly) {
//flag this record dirty so it gets picked up by save
this.value.items[this.activeWoItemIndex].loans[
this.activeItemIndex
].isDirty = true;
window.$gz.form.fieldValueChanged(this.pvm, ref);
}
},
itemRowClasses: function(item) {
let ret = "";
const isDeleted =
this.value.items[this.activeWoItemIndex].loans[item.index].deleted ===
true;
const hasError = this.form().childRowHasError(
this,
`Items[${this.activeWoItemIndex}].Loans[${item.index}].`
);
if (isDeleted) {
ret += this.form().tableRowDeletedClass();
}
if (hasError) {
ret += this.form().tableRowErrorClass();
}
return ret;
}
//---
},
computed: {
isDeleted: function() {
if (
this.value.items[this.activeWoItemIndex].loans[this.activeItemIndex] ==
null
) {
this.setDefaultView();
return true;
}
return (
this.value.items[this.activeWoItemIndex].loans[this.activeItemIndex]
.deleted === true
);
},
parentDeleted: function() {
return this.value.items[this.activeWoItemIndex].deleted === true;
},
headerList: function() {
/*
If the column is a text, left-align it
If the column is a number or number + unit, (or date) right-align it (like excel)
*/
let headers = [];
if (this.form().showMe(this, "WorkOrderItemLoanUnit")) {
headers.push({
text: this.$ay.t("WorkOrderItemLoanUnit"),
align: "left",
value: "loanUnitViz"
});
}
if (
this.form().showMe(this, "WorkOrderItemLoanRate") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("WorkOrderItemLoanRate"),
align: "left",
value: "unitOfMeasureViz"
});
}
if (
this.form().showMe(this, "WorkOrderItemLoanQuantity") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("WorkOrderItemLoanQuantity"),
align: "right",
value: "quantity"
});
}
if (this.form().showMe(this, "WorkOrderItemLoanOutDate")) {
headers.push({
text: this.$ay.t("WorkOrderItemLoanOutDate"),
align: "right",
value: "outDate"
});
}
if (this.form().showMe(this, "WorkOrderItemLoanDueDate")) {
headers.push({
text: this.$ay.t("WorkOrderItemLoanDueDate"),
align: "right",
value: "dueDate"
});
}
if (this.form().showMe(this, "WorkOrderItemLoanReturnDate")) {
headers.push({
text: this.$ay.t("WorkOrderItemLoanReturnDate"),
align: "right",
value: "returnDate"
});
}
if (this.form().showMe(this, "WorkOrderItemLoanNotes")) {
headers.push({
text: this.$ay.t("WorkOrderItemLoanNotes"),
align: "left",
value: "notes"
});
}
if (
this.form().showMe(this, "LoanCost") &&
this.value.userCanViewLoanerCosts &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("Cost"),
align: "right",
value: "cost"
});
}
if (
this.form().showMe(this, "LoanListPrice") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("ListPrice"),
align: "right",
value: "listPrice"
});
}
if (
this.form().showMe(this, "LoanPriceOverride") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("PriceOverride"),
align: "right",
value: "priceOverride"
});
}
if (
this.form().showMe(this, "LoanPriceViz") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("Price"),
align: "right",
value: "priceViz"
});
}
if (
this.form().showMe(this, "WorkOrderItemLoanTaxCodeID") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("Tax"),
align: "left",
value: "taxCodeViz"
});
}
if (
this.form().showMe(this, "LoanNetViz") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("NetPrice"),
align: "right",
value: "netViz"
});
}
if (
this.form().showMe(this, "LoanTaxAViz") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("TaxAAmt"),
align: "right",
value: "taxAViz"
});
}
if (
this.form().showMe(this, "LoanTaxBViz") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("TaxBAmt"),
align: "right",
value: "taxBViz"
});
}
if (
this.form().showMe(this, "LoanLineTotalViz") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("LineTotal"),
align: "right",
value: "lineTotalViz"
});
}
return headers;
},
itemList: function() {
return this.value.items[this.activeWoItemIndex].loans.map((x, i) => {
return {
index: i,
id: x.id,
loanUnitViz: x.loanUnitViz,
unitOfMeasureViz: x.unitOfMeasureViz,
quantity: window.$gz.locale.decimalLocalized(
x.quantity,
this.pvm.languageName
),
outDate: window.$gz.locale.utcDateToShortDateAndTimeLocalized(
x.outDate,
this.pvm.timeZoneName,
this.pvm.languageName,
this.pvm.hour12
),
dueDate: window.$gz.locale.utcDateToShortDateAndTimeLocalized(
x.dueDate,
this.pvm.timeZoneName,
this.pvm.languageName,
this.pvm.hour12
),
returnDate: window.$gz.locale.utcDateToShortDateAndTimeLocalized(
x.returnDate,
this.pvm.timeZoneName,
this.pvm.languageName,
this.pvm.hour12
),
notes: window.$gz.util.truncateString(
x.notes,
this.pvm.maxTableNotesLength
),
cost: window.$gz.locale.currencyLocalized(
x.cost,
this.pvm.languageName,
this.pvm.currencyName
),
listPrice: window.$gz.locale.currencyLocalized(
x.listPrice,
this.pvm.languageName,
this.pvm.currencyName
),
priceViz: window.$gz.locale.currencyLocalized(
x.priceViz,
this.pvm.languageName,
this.pvm.currencyName
),
taxCodeViz: x.taxCodeViz,
priceOverride: window.$gz.locale.currencyLocalized(
x.priceOverride,
this.pvm.languageName,
this.pvm.currencyName
),
netViz: window.$gz.locale.currencyLocalized(
x.netViz,
this.pvm.languageName,
this.pvm.currencyName
),
taxAViz: window.$gz.locale.currencyLocalized(
x.taxAViz,
this.pvm.languageName,
this.pvm.currencyName
),
taxBViz: window.$gz.locale.currencyLocalized(
x.taxBViz,
this.pvm.languageName,
this.pvm.currencyName
),
lineTotalViz: window.$gz.locale.currencyLocalized(
x.lineTotalViz,
this.pvm.languageName,
this.pvm.currencyName
)
};
});
},
formState: function() {
return this.pvm.formState;
},
formCustomTemplateKey: function() {
return this.pvm.formCustomTemplateKey;
},
hasData: function() {
return this.value.items[this.activeWoItemIndex].loans.length > 0;
},
hasSelection: function() {
return this.activeItemIndex != null;
},
canAdd: function() {
return this.pvm.rights.change && !this.value.userIsRestrictedType;
},
canDelete: function() {
return (
this.activeItemIndex != null &&
this.canDeleteAll &&
!this.value.userIsRestrictedType
);
},
canDeleteAll: function() {
return this.pvm.rights.change && !this.value.userIsRestrictedType;
}
//----
}
};
</script>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,710 @@
<template>
<div v-if="value != null" class="mt-8">
<v-row>
<v-col cols="12">
<v-menu offset-y max-width="600px">
<template v-slot:activator="{ on, attrs }">
<div class="text-h6">
<v-icon large :color="hasData ? 'primary' : null" class="mr-2"
>$ayiUserClock</v-icon
>
{{ $ay.t("WorkOrderItemScheduledUserList") }}
<v-btn v-if="!parentDeleted" large icon v-bind="attrs" v-on="on">
<v-icon small color="primary">$ayiEllipsisV</v-icon>
</v-btn>
</div>
</template>
<v-list>
<v-list-item v-if="canAdd" @click="newItem">
<v-list-item-icon>
<v-icon>$ayiPlus</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ $ay.t("New") }}</v-list-item-title>
</v-list-item>
<v-list-item
v-if="hasSelection && !formState.readOnly"
@click="convertToLabor"
>
<v-list-item-icon>
<v-icon>$ayiHammer</v-icon>
</v-list-item-icon>
<v-list-item-title>{{
$ay.t("WorkOrderConvertScheduledUserToLabor")
}}</v-list-item-title>
</v-list-item>
<v-list-item
v-if="!formState.readOnly && hasMultipleItems"
@click="convertAllToLabor"
>
<v-list-item-icon>
<v-icon>$ayiHammer</v-icon>
</v-list-item-icon>
<v-list-item-title>{{
$ay.t("WorkOrderConvertAllScheduledUsersToLabor")
}}</v-list-item-title>
</v-list-item>
<v-list-item v-if="canDelete && isDeleted" @click="unDeleteItem">
<v-list-item-icon>
<v-icon>$ayiTrashRestoreAlt</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ $ay.t("Undelete") }}</v-list-item-title>
</v-list-item>
<v-list-item v-if="canDelete && !isDeleted" @click="deleteItem">
<v-list-item-icon>
<v-icon>$ayiTrashAlt</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ $ay.t("SoftDelete") }}</v-list-item-title>
</v-list-item>
<v-list-item v-if="canDeleteAll" @click="deleteAllItem">
<v-list-item-icon>
<v-icon>$ayiDumpster</v-icon>
</v-list-item-icon>
<v-list-item-title>{{
$ay.t("SoftDeleteAll")
}}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-col>
<template v-if="hasData">
<!-- ################################ SCHEDULED USERS TABLE ############################### -->
<v-col cols="12" class="mb-10">
<v-data-table
:headers="headerList"
:items="itemList"
item-key="index"
v-model="selectedRow"
class="elevation-1"
disable-pagination
disable-filtering
disable-sort
hide-default-footer
data-cy="scheduledUsersTable"
dense
:item-class="itemRowClasses"
@click:row="handleRowClick"
:show-select="$vuetify.breakpoint.xs"
single-select
>
</v-data-table>
</v-col>
</template>
<template v-if="hasData && hasSelection">
<div ref="scheduledusertopform"></div>
<v-btn
v-if="canDelete && isDeleted"
large
@click="unDeleteItem"
color="primary"
>{{ $ay.t("Undelete")
}}<v-icon right large>$ayiTrashRestoreAlt</v-icon></v-btn
>
<v-col
v-if="form().showMe(this, 'WorkOrderItemScheduledUserStartDate')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-date-time-picker
:label="$ay.t('WorkOrderItemScheduledUserStartDate')"
v-model="
value.items[activeWoItemIndex].scheduledUsers[activeItemIndex]
.startDate
"
:readonly="formState.readOnly || value.userIsRestrictedType"
:disabled="isDeleted"
:ref="
`Items[${activeWoItemIndex}].scheduledUsers[${activeItemIndex}].startDate`
"
data-cy="startDate"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].scheduledUsers[${activeItemIndex}].startDate`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].scheduledUsers[${activeItemIndex}].startDate`
)
"
></gz-date-time-picker>
</v-col>
<v-col
v-if="form().showMe(this, 'WorkOrderItemScheduledUserStopDate')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-date-time-picker
:label="$ay.t('WorkOrderItemScheduledUserStopDate')"
v-model="
value.items[activeWoItemIndex].scheduledUsers[activeItemIndex]
.stopDate
"
:readonly="formState.readOnly || value.userIsRestrictedType"
:disabled="isDeleted"
:ref="
`Items[${activeWoItemIndex}].scheduledUsers[${activeItemIndex}].stopDate`
"
data-cy="stopDate"
:rules="[
form().datePrecedence(
this,
`Items[${activeWoItemIndex}].scheduledUsers[${activeItemIndex}].startDate`,
`Items[${activeWoItemIndex}].scheduledUsers[${activeItemIndex}].stopDate`
)
]"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].scheduledUsers[${activeItemIndex}].stopDate`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].scheduledUsers[${activeItemIndex}].stopDate`
)
"
></gz-date-time-picker>
</v-col>
<v-col
v-if="
form().showMe(this, 'WorkOrderItemScheduledUserEstimatedQuantity')
"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-decimal
v-model="
value.items[activeWoItemIndex].scheduledUsers[activeItemIndex]
.estimatedQuantity
"
:readonly="
formState.readOnly || isDeleted || value.userIsRestrictedType
"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemScheduledUserEstimatedQuantity')"
:ref="
`Items[${activeWoItemIndex}].scheduledUsers[${activeItemIndex}].estimatedQuantity`
"
data-cy="scheduledUsers.EstimatedQuantity"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].scheduledUsers[${activeItemIndex}].estimatedQuantity`
)
"
:rules="[
form().decimalValid(
this,
`Items[${activeWoItemIndex}].scheduledUsers[${activeItemIndex}].estimatedQuantity`
),
form().required(
this,
`Items[${activeWoItemIndex}].scheduledUsers[${activeItemIndex}].estimatedQuantity`
)
]"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].scheduledUsers[${activeItemIndex}].estimatedQuantity`
)
"
></gz-decimal>
</v-col>
<v-col
v-if="form().showMe(this, 'WorkOrderItemScheduledUserUserID')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-pick-list
:aya-type="$ay.ayt().User"
variant="tech"
:show-edit-icon="!value.userIsRestrictedType"
v-model="
value.items[activeWoItemIndex].scheduledUsers[activeItemIndex]
.userId
"
:readonly="
formState.readOnly || isDeleted || value.userIsRestrictedType
"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemScheduledUserUserID')"
:ref="
`Items[${activeWoItemIndex}].scheduledUsers[${activeItemIndex}].userId`
"
data-cy="scheduledUsers.userid"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].scheduledUsers[${activeItemIndex}].userId`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].scheduledUsers[${activeItemIndex}].userId`
)
"
@update:name="userChange"
></gz-pick-list>
</v-col>
<v-col
v-if="form().showMe(this, 'WorkOrderItemScheduledUserServiceRateID')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-pick-list
:aya-type="$ay.ayt().ServiceRate"
:variant="'contractid:' + value.contractId"
:show-edit-icon="!value.userIsRestrictedType"
v-model="
value.items[activeWoItemIndex].scheduledUsers[activeItemIndex]
.serviceRateId
"
:readonly="
formState.readOnly || isDeleted || value.userIsRestrictedType
"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemScheduledUserServiceRateID')"
:ref="
`Items[${activeWoItemIndex}].scheduledUsers[${activeItemIndex}].serviceRateId`
"
data-cy="scheduledUsers.serviceRateId"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].scheduledUsers[${activeItemIndex}].serviceRateId`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].scheduledUsers[${activeItemIndex}].serviceRateId`
)
"
@update:name="rateChange"
></gz-pick-list>
</v-col>
</template>
</v-row>
</div>
</template>
<script>
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/* XXXeslint-disable */
////////////////////////////////////////////////////////////////////////////////////////////////////////////
export default {
created() {
this.setDefaultView();
},
data() {
return {
activeItemIndex: null,
selectedRow: []
};
},
props: {
value: {
default: null,
type: Object
},
pvm: {
default: null,
type: Object
},
activeWoItemIndex: {
default: null,
type: Number
},
gotoIndex: {
default: null,
type: Number
}
},
watch: {
activeWoItemIndex(val, oldVal) {
if (val != oldVal) {
this.setDefaultView();
}
},
gotoIndex(val, oldVal) {
if (val != oldVal) {
let gotoIndex = val;
if (val < 0) {
//it's a create request
gotoIndex = this.newItem();
}
this.selectedRow = [{ index: gotoIndex }];
this.activeItemIndex = gotoIndex;
this.$nextTick(() => {
const el = this.$refs.scheduledusertopform;
if (el) {
el.scrollIntoView({ behavior: "smooth" });
}
});
}
}
},
methods: {
convertAllToLabor() {
this.value.items[this.activeWoItemIndex].scheduledUsers.forEach(z => {
this.doConvertToLabor(z);
});
},
convertToLabor() {
this.doConvertToLabor(
this.value.items[this.activeWoItemIndex].scheduledUsers[
this.activeItemIndex
]
);
},
doConvertToLabor(s) {
this.value.items[this.activeWoItemIndex].labors.push({
id: 0,
concurrency: 0,
userId: s.userId,
serviceStartDate: s.startDate,
serviceStopDate: s.stopDate,
serviceRateId: s.serviceRateId,
serviceDetails: null,
serviceRateQuantity: s.estimatedQuantity,
noChargeQuantity: 0,
taxCodeSaleId: null,
price: 0,
priceOverride: null,
isDirty: true,
quoteItemId: this.value.items[this.activeWoItemIndex].id,
uid: Date.now(),
userViz: s.userViz,
serviceRateViz: s.serviceRateViz,
taxCodeViz: null
});
this.$emit("change");
},
userChange(newName) {
this.value.items[this.activeWoItemIndex].scheduledUsers[
this.activeItemIndex
].userViz = newName;
},
rateChange(newName) {
this.value.items[this.activeWoItemIndex].scheduledUsers[
this.activeItemIndex
].serviceRateViz = newName;
},
newItem() {
let newIndex = this.value.items[this.activeWoItemIndex].scheduledUsers
.length;
this.value.items[this.activeWoItemIndex].scheduledUsers.push({
id: 0,
concurrency: 0,
userId: this.$store.getters.isScheduleableUser
? this.$store.state.userId
: null,
estimatedQuantity: 0,
startDate: null,
stopDate: null,
serviceRateId: null,
isDirty: true,
quoteItemId: this.value.items[this.activeWoItemIndex].id,
uid: Date.now(),
userViz: this.$store.getters.isScheduleableUser
? this.$store.state.userName
: null,
serviceRateViz: null
});
this.$emit("change");
this.selectedRow = [{ index: newIndex }];
this.activeItemIndex = newIndex;
return newIndex; //for create new on goto
},
unDeleteItem() {
this.value.items[this.activeWoItemIndex].scheduledUsers[
this.activeItemIndex
].deleted = false;
this.setDefaultView();
},
deleteItem() {
this.value.items[this.activeWoItemIndex].scheduledUsers[
this.activeItemIndex
].deleted = true;
this.setDefaultView();
this.$emit("change");
},
deleteAllItem() {
this.value.items[this.activeWoItemIndex].scheduledUsers.forEach(
z => (z.deleted = true)
);
this.setDefaultView();
this.$emit("change");
},
setDefaultView: function() {
//if only one record left then display it otherwise just let the datatable show what the user can click on
if (this.value.items[this.activeWoItemIndex].scheduledUsers.length == 1) {
this.selectedRow = [{ index: 0 }];
this.activeItemIndex = 0;
} else {
this.selectedRow = [];
this.activeItemIndex = null; //select nothing in essence resetting a child selects and this one too clearing form
}
},
handleRowClick: function(item) {
this.activeItemIndex = item.index;
this.selectedRow = [{ index: item.index }];
},
form() {
return window.$gz.form;
},
fieldValueChanged(ref) {
if (!this.formState.loading && !this.formState.readonly) {
//flag this record dirty so it gets picked up by save
this.value.items[this.activeWoItemIndex].scheduledUsers[
this.activeItemIndex
].isDirty = true;
window.$gz.form.fieldValueChanged(this.pvm, ref);
//------- SPECIAL HANDLING OF CHANGES -----------
const isNew =
this.value.items[this.activeWoItemIndex].scheduledUsers[
this.activeItemIndex
].id == 0;
//Auto calculate dates / quantities / global defaults
const dStart = this.value.items[this.activeWoItemIndex].scheduledUsers[
this.activeItemIndex
].startDate;
const dStop = this.value.items[this.activeWoItemIndex].scheduledUsers[
this.activeItemIndex
].stopDate;
if (ref.includes("startDate") && dStart != null) {
this.handleStartDateChange(isNew, dStart, dStop);
}
if (ref.includes("stopDate") && dStop != null) {
this.handleStopDateChange(isNew, dStart, dStop);
}
//------------------------------------------------------
}
},
handleStartDateChange: function(isNew, dStart, dStop) {
const globalMinutes =
window.$gz.store.state.globalSettings.workLaborScheduleDefaultMinutes;
if (isNew && dStop == null) {
if (globalMinutes != 0) {
//set stop date based on start date and global minutes
this.value.items[this.activeWoItemIndex].scheduledUsers[
this.activeItemIndex
].stopDate = window.$gz.locale.addMinutesToUTC8601String(
dStart,
globalMinutes
);
this.value.items[this.activeWoItemIndex].scheduledUsers[
this.activeItemIndex
].estimatedQuantity = globalMinutes;
}
} else {
//Existing record or both dates filled, just update quantity
if (dStop != null) {
this.value.items[this.activeWoItemIndex].scheduledUsers[
this.activeItemIndex
].estimatedQuantity = window.$gz.locale.diffHoursFromUTC8601String(
dStart,
dStop
);
}
}
},
handleStopDateChange: function(isNew, dStart, dStop) {
const globalMinutes =
window.$gz.store.state.globalSettings.workLaborScheduleDefaultMinutes;
if (isNew && dStart == null) {
if (globalMinutes != 0) {
//set start date based on stop date and global minutes
this.value.items[this.activeWoItemIndex].scheduledUsers[
this.activeItemIndex
].startDate = window.$gz.locale.addMinutesToUTC8601String(
dStop,
0 - globalMinutes
);
this.value.items[this.activeWoItemIndex].scheduledUsers[
this.activeItemIndex
].estimatedQuantity = globalMinutes;
}
} else {
//Existing record or both dates filled, just update quantity
if (dStart != null) {
this.value.items[this.activeWoItemIndex].scheduledUsers[
this.activeItemIndex
].estimatedQuantity = window.$gz.locale.diffHoursFromUTC8601String(
dStart,
dStop
);
}
}
},
itemRowClasses: function(item) {
let ret = "";
const isDeleted =
this.value.items[this.activeWoItemIndex].scheduledUsers[item.index]
.deleted === true;
const hasError = this.form().childRowHasError(
this,
`Items[${this.activeWoItemIndex}].ScheduledUsers[${item.index}].`
);
if (isDeleted) {
ret += this.form().tableRowDeletedClass();
}
if (hasError) {
ret += this.form().tableRowErrorClass();
}
return ret;
}
},
computed: {
isDeleted: function() {
if (
this.value.items[this.activeWoItemIndex].scheduledUsers[
this.activeItemIndex
] == null
) {
this.setDefaultView();
return true;
}
return (
this.value.items[this.activeWoItemIndex].scheduledUsers[
this.activeItemIndex
].deleted === true
);
},
parentDeleted: function() {
return this.value.items[this.activeWoItemIndex].deleted === true;
},
headerList: function() {
/*
If the column is a text, left-align it
If the column is a number or number + unit, (or date) right-align it (like excel)
*/
let headers = [];
if (this.form().showMe(this, "WorkOrderItemScheduledUserStartDate")) {
headers.push({
text: this.$ay.t("WorkOrderItemScheduledUserStartDate"),
align: "right",
value: "startDate"
});
}
if (this.form().showMe(this, "WorkOrderItemScheduledUserStopDate")) {
headers.push({
text: this.$ay.t("WorkOrderItemScheduledUserStopDate"),
align: "right",
value: "stopDate"
});
}
if (
this.form().showMe(this, "WorkOrderItemScheduledUserEstimatedQuantity")
) {
headers.push({
text: this.$ay.t("WorkOrderItemScheduledUserEstimatedQuantity"),
align: "right",
value: "estimatedQuantity"
});
}
if (this.form().showMe(this, "WorkOrderItemScheduledUserUserID")) {
headers.push({
text: this.$ay.t("WorkOrderItemScheduledUserUserID"),
align: "left",
value: "userViz"
});
}
if (this.form().showMe(this, "WorkOrderItemScheduledUserServiceRateID")) {
headers.push({
text: this.$ay.t("WorkOrderItemScheduledUserServiceRateID"),
align: "left",
value: "serviceRateViz"
});
}
return headers;
},
itemList: function() {
return this.value.items[this.activeWoItemIndex].scheduledUsers.map(
(x, i) => {
return {
index: i,
id: x.id,
startDate: window.$gz.locale.utcDateToShortDateAndTimeLocalized(
x.startDate,
this.pvm.timeZoneName,
this.pvm.languageName,
this.pvm.hour12
),
stopDate: window.$gz.locale.utcDateToShortDateAndTimeLocalized(
x.stopDate,
this.pvm.timeZoneName,
this.pvm.languageName,
this.pvm.hour12
),
estimatedQuantity: window.$gz.locale.decimalLocalized(
x.estimatedQuantity,
this.pvm.languageName
),
userViz: x.userViz,
serviceRateViz: x.serviceRateViz
};
}
);
},
formState: function() {
return this.pvm.formState;
},
formCustomTemplateKey: function() {
return this.pvm.formCustomTemplateKey;
},
hasData: function() {
return this.value.items[this.activeWoItemIndex].scheduledUsers.length > 0;
},
hasSelection: function() {
return this.activeItemIndex != null;
},
canAdd: function() {
return this.pvm.rights.change && !this.value.userIsRestrictedType;
},
canDelete: function() {
return (
this.activeItemIndex != null &&
this.canDeleteAll &&
!this.value.userIsRestrictedType
);
},
canDeleteAll: function() {
return this.pvm.rights.change && !this.value.userIsRestrictedType;
},
hasMultipleItems: function() {
return this.value.items[this.activeWoItemIndex].scheduledUsers.length > 1;
}
}
};
</script>

View File

@@ -0,0 +1,644 @@
<template>
<div v-if="value != null" class="mt-8">
<v-row>
<v-col cols="12">
<v-menu offset-y max-width="600px">
<template v-slot:activator="{ on, attrs }">
<div class="text-h6">
<v-icon large :color="hasData ? 'primary' : null" class="mr-2"
>$ayiTasks</v-icon
>
{{ $ay.t("WorkOrderItemTasks") }}
<v-btn v-if="!parentDeleted" large icon v-bind="attrs" v-on="on">
<v-icon small color="primary">$ayiEllipsisV</v-icon>
</v-btn>
</div>
</template>
<v-list>
<v-list-item v-if="canAdd" @click="newItem">
<v-list-item-icon>
<v-icon>$ayiPlus</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ $ay.t("New") }}</v-list-item-title>
</v-list-item>
<v-list-item v-if="canAdd" @click="taskGroupDialog = true">
<v-list-item-icon>
<v-icon>$ayiTasks</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ $ay.t("TaskGroup") }}</v-list-item-title>
</v-list-item>
<v-list-item v-if="canDelete && isDeleted" @click="unDeleteItem">
<v-list-item-icon>
<v-icon>$ayiTrashRestoreAlt</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ $ay.t("Undelete") }}</v-list-item-title>
</v-list-item>
<v-list-item v-if="canDelete && !isDeleted" @click="deleteItem">
<v-list-item-icon>
<v-icon>$ayiTrashAlt</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ $ay.t("SoftDelete") }}</v-list-item-title>
</v-list-item>
<v-list-item v-if="canDeleteAll" @click="deleteAllItem">
<v-list-item-icon>
<v-icon>$ayiDumpster</v-icon>
</v-list-item-icon>
<v-list-item-title>{{
$ay.t("SoftDeleteAll")
}}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-col>
<template v-if="hasData">
<!-- ############################################################### -->
<v-col cols="12" class="mb-10">
<v-data-table
:headers="headerList"
:items="itemList"
item-key="index"
v-model="selectedRow"
class="elevation-1"
disable-pagination
disable-filtering
disable-sort
hide-default-footer
data-cy="expensesTable"
dense
:item-class="itemRowClasses"
@click:row="handleRowClick"
:show-select="$vuetify.breakpoint.xs"
single-select
>
</v-data-table>
</v-col>
</template>
<template v-if="hasData && hasSelection">
<div ref="tasktopform"></div>
<v-btn
v-if="canDelete && isDeleted"
large
@click="unDeleteItem"
color="primary"
>{{ $ay.t("Undelete")
}}<v-icon right large>$ayiTrashRestoreAlt</v-icon></v-btn
>
<v-col
v-if="form().showMe(this, 'WorkOrderItemTaskSequence')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<v-text-field
v-model="
value.items[activeWoItemIndex].tasks[activeItemIndex].sequence
"
:readonly="formState.readOnly || value.userIsRestrictedType"
:disabled="isDeleted"
:label="$ay.t('Sequence')"
:ref="
`Items[${activeWoItemIndex}].tasks[${activeItemIndex}].sequence`
"
:rules="[
form().integerValid(
this,
`Items[${activeWoItemIndex}].tasks[${activeItemIndex}].sequence`
)
]"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].tasks[${activeItemIndex}].sequence`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].tasks[${activeItemIndex}].sequence`
)
"
type="number"
></v-text-field>
</v-col>
<v-col
v-if="
form().showMe(
this,
'WorkOrderItemTaskWorkOrderItemTaskCompletionType'
)
"
cols="12"
sm="6"
lg="4"
xl="3"
>
<v-select
v-model="
value.items[activeWoItemIndex].tasks[activeItemIndex].status
"
:items="pvm.selectLists.woItemTaskCompletionTypes"
item-text="name"
item-value="id"
:readonly="formState.readOnly || isDeleted"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemTaskWorkOrderItemTaskCompletionType')"
:ref="
`Items[${activeWoItemIndex}].tasks[${activeItemIndex}].status`
"
data-cy="usertype"
:rules="[
form().integerValid(
this,
`Items[${activeWoItemIndex}].tasks[${activeItemIndex}].status`
)
]"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].tasks[${activeItemIndex}].status`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].tasks[${activeItemIndex}].status`
)
"
@change="statusChange"
></v-select>
</v-col>
<v-col
v-if="form().showMe(this, 'WorkOrderItemTaskUser')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-pick-list
:aya-type="$ay.ayt().User"
variant="tech"
:show-edit-icon="!value.userIsRestrictedType"
v-model="
value.items[activeWoItemIndex].tasks[activeItemIndex]
.completedByUserId
"
:readonly="
formState.readOnly || isDeleted || value.userIsRestrictedType
"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemTaskUser')"
:ref="
`Items[${activeWoItemIndex}].tasks[${activeItemIndex}].completedByUserId`
"
data-cy="expenseUser"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].tasks[${activeItemIndex}].completedByUserId`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].tasks[${activeItemIndex}].completedByUserId`
)
"
@update:name="userChange"
></gz-pick-list>
</v-col>
<v-col
v-if="form().showMe(this, 'WorkOrderItemTaskCompletedDate')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-date-time-picker
:label="$ay.t('WorkOrderItemTaskCompletedDate')"
v-model="
value.items[activeWoItemIndex].tasks[activeItemIndex]
.completedDate
"
:readonly="formState.readOnly"
:disabled="isDeleted"
:ref="
`Items[${activeWoItemIndex}].tasks[${activeItemIndex}].completedDate`
"
data-cy="travelCompletedDate"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].tasks[${activeItemIndex}].completedDate`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].tasks[${activeItemIndex}].completedDate`
)
"
></gz-date-time-picker>
</v-col>
<v-col v-if="form().showMe(this, 'WorkOrderItemTaskTaskID')" cols="12">
<v-textarea
v-model="value.items[activeWoItemIndex].tasks[activeItemIndex].task"
:readonly="formState.readOnly || value.userIsRestrictedType"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemTaskTaskID')"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].tasks[${activeItemIndex}].task`
)
"
:rules="[
form().required(
this,
`Items[${activeWoItemIndex}].tasks[${activeItemIndex}].task`
)
]"
:ref="`Items[${activeWoItemIndex}].tasks[${activeItemIndex}].task`"
data-cy="task"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].tasks[${activeItemIndex}].task`
)
"
auto-grow
></v-textarea>
</v-col>
</template>
</v-row>
<!-- ################################################################################-->
<!-- ########################## GROUP SELECTOR FORM ###############################-->
<!-- ################################################################################-->
<template>
<v-row justify="center">
<v-dialog max-width="600px" v-model="taskGroupDialog">
<v-card>
<v-card-title> </v-card-title>
<v-card-text>
<gz-pick-list
:aya-type="$ay.ayt().TaskGroup"
show-edit-icon
v-model="selectedTaskGroup"
:label="$ay.t('TaskGroupList')"
ref="selectedTaskGroup"
data-cy="selectedTaskGroup"
></gz-pick-list>
</v-card-text>
<v-card-actions>
<v-btn text @click="taskGroupDialog = false" color="primary">{{
$ay.t("Cancel")
}}</v-btn>
<v-spacer></v-spacer>
<v-btn
:disabled="selectedTaskGroup == null"
color="primary"
text
@click="addTaskGroup()"
class="ml-4"
>{{ $ay.t("Add") }}</v-btn
>
</v-card-actions>
</v-card>
</v-dialog>
</v-row>
</template>
</div>
</template>
<script>
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/* XXXeslint-disable */
////////////////////////////////////////////////////////////////////////////////////////////////////////////
export default {
created() {
this.setDefaultView();
},
data() {
return {
activeItemIndex: null,
selectedRow: [],
taskGroupDialog: false,
selectedTaskGroup: null
};
},
props: {
value: {
default: null,
type: Object
},
pvm: {
default: null,
type: Object
},
activeWoItemIndex: {
default: null,
type: Number
},
gotoIndex: {
default: null,
type: Number
}
},
watch: {
activeWoItemIndex(val, oldVal) {
if (val != oldVal) {
this.setDefaultView();
}
},
gotoIndex(val, oldVal) {
if (val != oldVal) {
let gotoIndex = val;
if (val < 0) {
//it's a create request
gotoIndex = this.newItem();
}
this.selectedRow = [{ index: gotoIndex }];
this.activeItemIndex = gotoIndex;
this.$nextTick(() => {
const el = this.$refs.tasktopform;
if (el) {
el.scrollIntoView({ behavior: "smooth" });
}
});
}
}
},
methods: {
async addTaskGroup() {
let res = await window.$gz.api.get(
`task-group/${this.selectedTaskGroup}`
);
if (res.data && res.data.items) {
let newIndex = this.value.items[this.activeWoItemIndex].tasks.length;
let incompleteViz = this.pvm.selectLists.woItemTaskCompletionTypes.find(
s => s.id == 1 //incomplete
).name;
res.data.items.forEach(z => {
newIndex++;
this.value.items[this.activeWoItemIndex].tasks.push({
id: 0,
concurrency: 0,
sequence: newIndex,
task: z.task,
status: 1, //incomplete==1
statusViz: incompleteViz,
completedByUserId: null,
completedDate: null,
isDirty: true,
quoteItemId: this.value.items[this.activeWoItemIndex].id,
uid: Date.now(),
completedByUserViz: null
});
});
this.$emit("change");
this.selectedRow = [{ index: newIndex }];
this.activeItemIndex = newIndex;
}
this.taskGroupDialog = false;
},
userChange(newName) {
this.value.items[this.activeWoItemIndex].tasks[
this.activeItemIndex
].completedByUserViz = newName;
},
statusChange(newStatusId) {
this.value.items[this.activeWoItemIndex].tasks[
this.activeItemIndex
].statusViz = this.pvm.selectLists.woItemTaskCompletionTypes.find(
s => s.id == newStatusId
).name;
//completed date auto set by status change if not a todo
if (newStatusId != 1) {
this.value.items[this.activeWoItemIndex].tasks[
this.activeItemIndex
].completedDate = window.$gz.locale.nowUTC8601String();
} else {
this.value.items[this.activeWoItemIndex].tasks[
this.activeItemIndex
].completedDate = null;
}
},
newItem() {
let newIndex = this.value.items[this.activeWoItemIndex].tasks.length;
this.value.items[this.activeWoItemIndex].tasks.push({
id: 0,
concurrency: 0,
sequence: newIndex + 1, //indexes are zero based but sequences are visible to user so 1 based
task: undefined, //to trigger validation on new
status: 1, //incomplete==1
completedByUserId: null,
completedDate: null,
isDirty: true,
quoteItemId: this.value.items[this.activeWoItemIndex].id,
uid: Date.now(),
completedByUserViz: null,
statusViz: null
});
this.$emit("change");
this.selectedRow = [{ index: newIndex }];
this.activeItemIndex = newIndex;
//trigger rule breaking / validation
this.$nextTick(() => {
this.value.items[this.activeWoItemIndex].tasks[
this.activeItemIndex
].task = null;
this.fieldValueChanged(
`Items[${this.activeWoItemIndex}].tasks[${this.activeItemIndex}].task`
);
});
return newIndex; //for create new on goto
},
unDeleteItem() {
this.value.items[this.activeWoItemIndex].tasks[
this.activeItemIndex
].deleted = false;
this.setDefaultView();
},
deleteItem() {
this.value.items[this.activeWoItemIndex].tasks[
this.activeItemIndex
].deleted = true;
this.setDefaultView();
this.$emit("change");
},
deleteAllItem() {
this.value.items[this.activeWoItemIndex].tasks.forEach(
z => (z.deleted = true)
);
this.setDefaultView();
this.$emit("change");
},
setDefaultView: function() {
//if only one record left then display it otherwise just let the datatable show what the user can click on
if (this.value.items[this.activeWoItemIndex].tasks.length == 1) {
this.selectedRow = [{ index: 0 }];
this.activeItemIndex = 0;
} else {
this.selectedRow = [];
this.activeItemIndex = null; //select nothing in essence resetting a child selects and this one too clearing form
}
},
handleRowClick: function(item) {
this.activeItemIndex = item.index;
this.selectedRow = [{ index: item.index }];
},
form() {
return window.$gz.form;
},
fieldValueChanged(ref) {
if (!this.formState.loading && !this.formState.readonly) {
//flag this record dirty so it gets picked up by save
this.value.items[this.activeWoItemIndex].tasks[
this.activeItemIndex
].isDirty = true;
window.$gz.form.fieldValueChanged(this.pvm, ref);
}
},
itemRowClasses: function(item) {
let ret = "";
const isDeleted =
this.value.items[this.activeWoItemIndex].tasks[item.index].deleted ===
true;
const hasError = this.form().childRowHasError(
this,
`Items[${this.activeWoItemIndex}].Tasks[${item.index}].`
);
if (isDeleted) {
ret += this.form().tableRowDeletedClass();
}
if (hasError) {
ret += this.form().tableRowErrorClass();
}
return ret;
}
},
computed: {
isDeleted: function() {
if (
this.value.items[this.activeWoItemIndex].tasks[this.activeItemIndex] ==
null
) {
this.setDefaultView();
return true;
}
return (
this.value.items[this.activeWoItemIndex].tasks[this.activeItemIndex]
.deleted === true
);
},
parentDeleted: function() {
return this.value.items[this.activeWoItemIndex].deleted === true;
},
headerList: function() {
/*
If the column is a text, left-align it
If the column is a number or number + unit, (or date) right-align it (like excel)
*/
let headers = [];
if (this.form().showMe(this, "WorkOrderItemTaskSequence")) {
headers.push({
text: this.$ay.t("Sequence"),
align: "left",
value: "sequence"
});
}
if (this.form().showMe(this, "WorkOrderItemTaskTaskID")) {
headers.push({
text: this.$ay.t("WorkOrderItemTaskTaskID"),
align: "start",
value: "task"
});
}
if (this.form().showMe(this, "WorkOrderItemTaskUser")) {
headers.push({
text: this.$ay.t("WorkOrderItemTaskUser"),
align: "start",
value: "completedByUserViz"
});
}
if (
this.form().showMe(
this,
"WorkOrderItemTaskWorkOrderItemTaskCompletionType"
)
) {
headers.push({
text: this.$ay.t("WorkOrderItemTaskWorkOrderItemTaskCompletionType"),
align: "start",
value: "statusViz"
});
}
if (this.form().showMe(this, "WorkOrderItemTaskCompletedDate")) {
headers.push({
text: this.$ay.t("WorkOrderItemTaskCompletedDate"),
align: "right",
value: "completedDate"
});
}
return headers;
},
itemList: function() {
return this.value.items[this.activeWoItemIndex].tasks
.map((x, i) => {
return {
index: i,
id: x.id,
sequence: x.sequence,
task: x.task,
completedByUserViz: x.completedByUserViz,
statusViz: x.statusViz,
completedDate: window.$gz.locale.utcDateToShortDateAndTimeLocalized(
x.completedDate,
this.pvm.timeZoneName,
this.pvm.languageName,
this.pvm.hour12
)
};
})
.sort((a, b) => a.sequence - b.sequence);
},
formState: function() {
return this.pvm.formState;
},
formCustomTemplateKey: function() {
return this.pvm.formCustomTemplateKey;
},
hasData: function() {
return this.value.items[this.activeWoItemIndex].tasks.length > 0;
},
hasSelection: function() {
return this.activeItemIndex != null;
},
canAdd: function() {
return this.pvm.rights.change && !this.value.userIsRestrictedType;
},
canDelete: function() {
return (
this.activeItemIndex != null &&
this.canDeleteAll &&
!this.value.userIsRestrictedType
);
},
canDeleteAll: function() {
return this.pvm.rights.change && !this.value.userIsRestrictedType;
}
}
};
</script>

View File

@@ -0,0 +1,976 @@
<template>
<div v-if="value != null" class="mt-8">
<v-row>
<v-col cols="12">
<v-menu offset-y max-width="600px">
<template v-slot:activator="{ on, attrs }">
<div class="text-h6">
<v-icon large :color="hasData ? 'primary' : null" class="mr-2"
>$ayiTruckMonster</v-icon
>
{{ $ay.t("WorkOrderItemTravelList") }}
<v-btn v-if="!parentDeleted" large icon v-bind="attrs" v-on="on">
<v-icon small color="primary">$ayiEllipsisV</v-icon>
</v-btn>
</div>
</template>
<v-list>
<v-list-item v-if="canAdd" @click="newItem">
<v-list-item-icon>
<v-icon>$ayiPlus</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ $ay.t("New") }}</v-list-item-title>
</v-list-item>
<v-list-item v-if="canDelete && isDeleted" @click="unDeleteItem">
<v-list-item-icon>
<v-icon>$ayiTrashRestoreAlt</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ $ay.t("Undelete") }}</v-list-item-title>
</v-list-item>
<v-list-item v-if="canDelete && !isDeleted" @click="deleteItem">
<v-list-item-icon>
<v-icon>$ayiTrashAlt</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ $ay.t("SoftDelete") }}</v-list-item-title>
</v-list-item>
<v-list-item v-if="canDeleteAll" @click="deleteAllItem">
<v-list-item-icon>
<v-icon>$ayiDumpster</v-icon>
</v-list-item-icon>
<v-list-item-title>{{
$ay.t("SoftDeleteAll")
}}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-col>
<template v-if="hasData">
<!-- ############################################################### -->
<v-col cols="12" class="mb-10">
<v-data-table
:headers="headerList"
:items="itemList"
item-key="index"
v-model="selectedRow"
class="elevation-1"
disable-pagination
disable-filtering
disable-sort
hide-default-footer
data-cy="travelsTable"
dense
:item-class="itemRowClasses"
@click:row="handleRowClick"
:show-select="$vuetify.breakpoint.xs"
single-select
>
</v-data-table>
</v-col>
</template>
<template v-if="hasData && hasSelection">
<div ref="traveltopform"></div>
<v-btn
v-if="canDelete && isDeleted"
large
@click="unDeleteItem"
color="primary"
>{{ $ay.t("Undelete")
}}<v-icon right large>$ayiTrashRestoreAlt</v-icon></v-btn
>
<v-col
v-if="form().showMe(this, 'WorkOrderItemTravelStartDate')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-date-time-picker
:label="$ay.t('WorkOrderItemTravelStartDate')"
v-model="
value.items[activeWoItemIndex].travels[activeItemIndex]
.travelStartDate
"
:readonly="formState.readOnly"
:disabled="isDeleted"
:ref="
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].travelStartDate`
"
data-cy="travelStartDate"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].travelStartDate`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].travelStartDate`
)
"
></gz-date-time-picker>
</v-col>
<v-col
v-if="form().showMe(this, 'WorkOrderItemTravelStopDate')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-date-time-picker
:label="$ay.t('WorkOrderItemTravelStopDate')"
v-model="
value.items[activeWoItemIndex].travels[activeItemIndex]
.travelStopDate
"
:readonly="formState.readOnly"
:disabled="isDeleted"
:ref="
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].travelStopDate`
"
data-cy="travelStopDate"
:rules="[
form().datePrecedence(
this,
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].travelStartDate`,
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].travelStopDate`
)
]"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].travelStopDate`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].travelStopDate`
)
"
></gz-date-time-picker>
</v-col>
<v-col
v-if="form().showMe(this, 'WorkOrderItemTravelRateQuantity')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-decimal
v-model="
value.items[activeWoItemIndex].travels[activeItemIndex]
.travelRateQuantity
"
:readonly="formState.readOnly || isDeleted"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemTravelRateQuantity')"
:ref="
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].travelRateQuantity`
"
data-cy="travelTravelRateQuantity"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].travelRateQuantity`
)
"
:rules="[
form().decimalValid(
this,
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].travelRateQuantity`
),
form().required(
this,
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].travelRateQuantity`
)
]"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].travelRateQuantity`
)
"
></gz-decimal>
</v-col>
<v-col
v-if="form().showMe(this, 'WorkOrderItemTravelServiceRateID')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-pick-list
:aya-type="$ay.ayt().TravelRate"
:variant="'contractid:' + value.contractId"
:show-edit-icon="!value.userIsRestrictedType"
v-model="
value.items[activeWoItemIndex].travels[activeItemIndex]
.travelRateId
"
:readonly="formState.readOnly || isDeleted"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemTravelServiceRateID')"
:ref="
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].travelRateId`
"
data-cy="travels.travelRateId"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].travelRateId`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].travelRateId`
)
"
@update:name="rateChange"
></gz-pick-list>
</v-col>
<v-col
v-if="form().showMe(this, 'WorkOrderItemTravelUserID')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-pick-list
:aya-type="$ay.ayt().User"
variant="tech"
:show-edit-icon="!value.userIsRestrictedType"
v-model="
value.items[activeWoItemIndex].travels[activeItemIndex].userId
"
:readonly="
formState.readOnly || isDeleted || value.userIsRestrictedType
"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemTravelUserID')"
:ref="
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].userId`
"
data-cy="travels.userid"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].userId`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].userId`
)
"
@update:name="userChange"
></gz-pick-list>
</v-col>
<v-col
v-if="form().showMe(this, 'WorkOrderItemTravelNoChargeQuantity')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-decimal
v-model="
value.items[activeWoItemIndex].travels[activeItemIndex]
.noChargeQuantity
"
:readonly="formState.readOnly || isDeleted"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemTravelNoChargeQuantity')"
:ref="
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].noChargeQuantity`
"
data-cy="travelNoChargeQuantity"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].noChargeQuantity`
)
"
:rules="[
form().decimalValid(
this,
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].noChargeQuantity`
),
form().required(
this,
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].noChargeQuantity`
)
]"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].noChargeQuantity`
)
"
></gz-decimal>
</v-col>
<v-col
v-if="
form().showMe(this, 'WorkOrderItemTravelTaxRateSaleID') &&
!value.userIsRestrictedType
"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-pick-list
:aya-type="$ay.ayt().TaxCode"
show-edit-icon
v-model="
value.items[activeWoItemIndex].travels[activeItemIndex]
.taxCodeSaleId
"
:readonly="formState.readOnly || isDeleted"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemTravelTaxRateSaleID')"
:ref="
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].taxCodeSaleId`
"
data-cy="travelTaxCodeSaleId"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].taxCodeSaleId`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].taxCodeSaleId`
)
"
@update:name="taxCodeChange"
></gz-pick-list>
</v-col>
<v-col
v-if="
form().showMe(this, 'TravelPriceOverride') &&
!value.userIsRestrictedType
"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-currency
v-model="
value.items[activeWoItemIndex].travels[activeItemIndex]
.priceOverride
"
can-clear
:readonly="formState.readOnly || isDeleted"
:disabled="isDeleted"
:label="$ay.t('PriceOverride')"
:ref="
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].priceOverride`
"
data-cy="travelpriceoverride"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].priceOverride`
)
"
:rules="[
form().decimalValid(
this,
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].priceOverride`
)
]"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].priceOverride`
)
"
></gz-currency>
</v-col>
<v-col
v-if="form().showMe(this, 'WorkOrderItemTravelDetails')"
cols="12"
>
<v-textarea
v-model="
value.items[activeWoItemIndex].travels[activeItemIndex]
.travelDetails
"
:readonly="formState.readOnly"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemTravelDetails')"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].travelDetails`
)
"
:ref="
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].travelDetails`
"
data-cy="traveltravelDetails"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].travels[${activeItemIndex}].travelDetails`
)
"
auto-grow
></v-textarea>
</v-col>
</template>
</v-row>
</div>
</template>
<script>
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/* XXXeslint-disable */
////////////////////////////////////////////////////////////////////////////////////////////////////////////
export default {
created() {
this.setDefaultView();
},
data() {
return {
activeItemIndex: null,
selectedRow: []
};
},
props: {
value: {
default: null,
type: Object
},
pvm: {
default: null,
type: Object
},
activeWoItemIndex: {
default: null,
type: Number
},
gotoIndex: {
default: null,
type: Number
}
},
watch: {
activeWoItemIndex(val, oldVal) {
if (val != oldVal) {
this.setDefaultView();
}
},
gotoIndex(val, oldVal) {
if (val != oldVal) {
let gotoIndex = val;
if (val < 0) {
//it's a create request
gotoIndex = this.newItem();
}
this.selectedRow = [{ index: gotoIndex }];
this.activeItemIndex = gotoIndex;
this.$nextTick(() => {
const el = this.$refs.traveltopform;
if (el) {
el.scrollIntoView({ behavior: "smooth" });
}
});
}
}
},
methods: {
userChange(newName) {
this.value.items[this.activeWoItemIndex].travels[
this.activeItemIndex
].userViz = newName;
},
rateChange(newName) {
this.value.items[this.activeWoItemIndex].travels[
this.activeItemIndex
].travelRateViz = newName;
},
taxCodeChange(newName) {
this.value.items[this.activeWoItemIndex].travels[
this.activeItemIndex
].taxCodeViz = newName;
},
newItem() {
let newIndex = this.value.items[this.activeWoItemIndex].travels.length;
this.value.items[this.activeWoItemIndex].travels.push({
id: 0,
concurrency: 0,
userId: this.$store.getters.isScheduleableUser
? this.$store.state.userId
: null,
travelStartDate: null,
travelStopDate: null,
travelRateId: null,
travelDetails: null,
travelRateQuantity: 0,
noChargeQuantity: 0,
taxCodeSaleId: null,
price: 0,
priceOverride: null,
isDirty: true,
quoteItemId: this.value.items[this.activeWoItemIndex].id,
uid: Date.now(),
userViz: this.$store.getters.isScheduleableUser
? this.$store.state.userName
: null,
travelRateViz: null,
taxCodeViz: null
});
this.$emit("change");
this.selectedRow = [{ index: newIndex }];
this.activeItemIndex = newIndex;
return newIndex; //for create new on goto
},
unDeleteItem() {
this.value.items[this.activeWoItemIndex].travels[
this.activeItemIndex
].deleted = false;
this.setDefaultView();
},
deleteItem() {
this.value.items[this.activeWoItemIndex].travels[
this.activeItemIndex
].deleted = true;
this.setDefaultView();
this.$emit("change");
},
deleteAllItem() {
this.value.items[this.activeWoItemIndex].travels.forEach(
z => (z.deleted = true)
);
this.setDefaultView();
this.$emit("change");
},
setDefaultView: function() {
//if only one record left then display it otherwise just let the datatable show what the user can click on
if (this.value.items[this.activeWoItemIndex].travels.length == 1) {
this.selectedRow = [{ index: 0 }];
this.activeItemIndex = 0;
} else {
this.selectedRow = [];
this.activeItemIndex = null; //select nothing in essence resetting a child selects and this one too clearing form
}
},
handleRowClick: function(item) {
this.activeItemIndex = item.index;
this.selectedRow = [{ index: item.index }];
},
form() {
return window.$gz.form;
},
fieldValueChanged(ref) {
if (!this.formState.loading && !this.formState.readonly) {
//flag this record dirty so it gets picked up by save
this.value.items[this.activeWoItemIndex].travels[
this.activeItemIndex
].isDirty = true;
window.$gz.form.fieldValueChanged(this.pvm, ref);
//------- SPECIAL HANDLING OF CHANGES -----------
const isNew =
this.value.items[this.activeWoItemIndex].travels[this.activeItemIndex]
.id == 0;
//Auto calculate dates / quantities / global defaults
const dStart = this.value.items[this.activeWoItemIndex].travels[
this.activeItemIndex
].travelStartDate;
const dStop = this.value.items[this.activeWoItemIndex].travels[
this.activeItemIndex
].travelStopDate;
if (ref.includes("travelStartDate") && dStart != null) {
this.handleStartDateChange(isNew, dStart, dStop);
}
if (ref.includes("travelStopDate") && dStop != null) {
this.handleStopDateChange(isNew, dStart, dStop);
}
//------------------------------------------------------
}
},
handleStartDateChange: function(isNew, dStart, dStop) {
const globalMinutes =
window.$gz.store.state.globalSettings.workOrderTravelDefaultMinutes;
if (isNew && dStop == null) {
if (globalMinutes != 0) {
//set stop date based on start date and global minutes
this.value.items[this.activeWoItemIndex].travels[
this.activeItemIndex
].travelStopDate = window.$gz.locale.addMinutesToUTC8601String(
dStart,
globalMinutes
);
this.value.items[this.activeWoItemIndex].travels[
this.activeItemIndex
].travelRateQuantity = globalMinutes;
}
} else {
//Existing record or both dates filled, just update quantity
if (dStop != null) {
this.value.items[this.activeWoItemIndex].travels[
this.activeItemIndex
].travelRateQuantity = window.$gz.locale.diffHoursFromUTC8601String(
dStart,
dStop
);
}
}
},
handleStopDateChange: function(isNew, dStart, dStop) {
const globalMinutes =
window.$gz.store.state.globalSettings.workOrderTravelDefaultMinutes;
if (isNew && dStart == null) {
if (globalMinutes != 0) {
//set start date based on stop date and global minutes
this.value.items[this.activeWoItemIndex].travels[
this.activeItemIndex
].travelStartDate = window.$gz.locale.addMinutesToUTC8601String(
dStop,
0 - globalMinutes
);
this.value.items[this.activeWoItemIndex].travels[
this.activeItemIndex
].travelRateQuantity = globalMinutes;
}
} else {
//Existing record or both dates filled, just update quantity
if (dStart != null) {
this.value.items[this.activeWoItemIndex].travels[
this.activeItemIndex
].travelRateQuantity = window.$gz.locale.diffHoursFromUTC8601String(
dStart,
dStop
);
}
}
},
itemRowClasses: function(item) {
let ret = "";
const isDeleted =
this.value.items[this.activeWoItemIndex].travels[item.index].deleted ===
true;
const hasError = this.form().childRowHasError(
this,
`Items[${this.activeWoItemIndex}].Travels[${item.index}].`
);
if (isDeleted) {
ret += this.form().tableRowDeletedClass();
}
if (hasError) {
ret += this.form().tableRowErrorClass();
}
return ret;
}
//---
},
computed: {
isDeleted: function() {
if (
this.value.items[this.activeWoItemIndex].travels[
this.activeItemIndex
] == null
) {
this.setDefaultView();
return true;
}
return (
this.value.items[this.activeWoItemIndex].travels[this.activeItemIndex]
.deleted === true
);
},
parentDeleted: function() {
return this.value.items[this.activeWoItemIndex].deleted === true;
},
headerList: function() {
/*
If the column is a text, left-align it
If the column is a number or number + unit, (or date) right-align it (like excel)
*/
let headers = [];
if (this.form().showMe(this, "WorkOrderItemTravelStartDate")) {
headers.push({
text: this.$ay.t("WorkOrderItemTravelStartDate"),
align: "right",
value: "travelStartDate"
});
}
if (this.form().showMe(this, "WorkOrderItemTravelStopDate")) {
headers.push({
text: this.$ay.t("WorkOrderItemTravelStopDate"),
align: "right",
value: "travelStopDate"
});
}
if (this.form().showMe(this, "WorkOrderItemTravelRateQuantity")) {
headers.push({
text: this.$ay.t("WorkOrderItemTravelRateQuantity"),
align: "right",
value: "travelRateQuantity"
});
}
if (this.form().showMe(this, "WorkOrderItemTravelServiceRateID")) {
headers.push({
text: this.$ay.t("WorkOrderItemTravelServiceRateID"),
align: "left",
value: "travelRateViz"
});
}
if (this.form().showMe(this, "WorkOrderItemTravelDetails")) {
headers.push({
text: this.$ay.t("WorkOrderItemTravelDetails"),
align: "left",
value: "travelDetails"
});
}
if (this.form().showMe(this, "WorkOrderItemTravelUserID")) {
headers.push({
text: this.$ay.t("WorkOrderItemTravelUserID"),
align: "left",
value: "userViz"
});
}
if (this.form().showMe(this, "WorkOrderItemTravelNoChargeQuantity")) {
headers.push({
text: this.$ay.t("WorkOrderItemTravelNoChargeQuantity"),
align: "right",
value: "noChargeQuantity"
});
}
if (this.form().showMe(this, "TravelUnitOfMeasureViz")) {
headers.push({
text: this.$ay.t("UnitOfMeasure"),
align: "left",
value: "unitOfMeasureViz"
});
}
if (
this.form().showMe(this, "TravelCostViz") &&
this.value.userCanViewLaborOrTravelRateCosts &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("Cost"),
align: "right",
value: "costViz"
});
}
if (
this.form().showMe(this, "TravelListPriceViz") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("ListPrice"),
align: "right",
value: "listPriceViz"
});
}
if (
this.form().showMe(this, "TravelPriceOverride") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("PriceOverride"),
align: "right",
value: "priceOverride"
});
}
if (
this.form().showMe(this, "TravelPriceViz") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("Price"),
align: "right",
value: "priceViz"
});
}
if (
this.form().showMe(this, "WorkOrderItemTravelTaxRateSaleID") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("Tax"),
align: "left",
value: "taxCodeViz"
});
}
if (
this.form().showMe(this, "TravelNetViz") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("NetPrice"),
align: "right",
value: "netViz"
});
}
if (
this.form().showMe(this, "TravelTaxAViz") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("TaxAAmt"),
align: "right",
value: "taxAViz"
});
}
if (
this.form().showMe(this, "TravelTaxBViz") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("TaxBAmt"),
align: "right",
value: "taxBViz"
});
}
if (
this.form().showMe(this, "TravelLineTotalViz") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("LineTotal"),
align: "right",
value: "lineTotalViz"
});
}
return headers;
},
itemList: function() {
return this.value.items[this.activeWoItemIndex].travels.map((x, i) => {
return {
index: i,
id: x.id,
travelStartDate: window.$gz.locale.utcDateToShortDateAndTimeLocalized(
x.travelStartDate,
this.pvm.timeZoneName,
this.pvm.languageName,
this.pvm.hour12
),
travelStopDate: window.$gz.locale.utcDateToShortDateAndTimeLocalized(
x.travelStopDate,
this.pvm.timeZoneName,
this.pvm.languageName,
this.pvm.hour12
),
travelRateQuantity: window.$gz.locale.decimalLocalized(
x.travelRateQuantity,
this.pvm.languageName
),
travelRateViz: x.travelRateViz,
userViz: x.userViz,
noChargeQuantity: window.$gz.locale.decimalLocalized(
x.noChargeQuantity,
this.pvm.languageName
),
unitOfMeasureViz: x.unitOfMeasureViz,
costViz: window.$gz.locale.currencyLocalized(
x.costViz,
this.pvm.languageName,
this.pvm.currencyName
),
listPriceViz: window.$gz.locale.currencyLocalized(
x.listPriceViz,
this.pvm.languageName,
this.pvm.currencyName
),
priceViz: window.$gz.locale.currencyLocalized(
x.priceViz,
this.pvm.languageName,
this.pvm.currencyName
),
taxCodeViz: x.taxCodeViz,
priceOverride: window.$gz.locale.currencyLocalized(
x.priceOverride,
this.pvm.languageName,
this.pvm.currencyName
),
netViz: window.$gz.locale.currencyLocalized(
x.netViz,
this.pvm.languageName,
this.pvm.currencyName
),
taxAViz: window.$gz.locale.currencyLocalized(
x.taxAViz,
this.pvm.languageName,
this.pvm.currencyName
),
taxBViz: window.$gz.locale.currencyLocalized(
x.taxBViz,
this.pvm.languageName,
this.pvm.currencyName
),
lineTotalViz: window.$gz.locale.currencyLocalized(
x.lineTotalViz,
this.pvm.languageName,
this.pvm.currencyName
),
travelDetails: window.$gz.util.truncateString(
x.travelDetails,
this.pvm.maxTableNotesLength
)
};
});
},
formState: function() {
return this.pvm.formState;
},
formCustomTemplateKey: function() {
return this.pvm.formCustomTemplateKey;
},
hasData: function() {
return this.value.items[this.activeWoItemIndex].travels.length > 0;
},
hasSelection: function() {
return this.activeItemIndex != null;
},
canAdd: function() {
return this.pvm.rights.change;
},
canDelete: function() {
return this.activeItemIndex != null && this.pvm.rights.change;
},
canDeleteAll: function() {
return this.pvm.rights.change && !this.value.userIsRestrictedType;
}
//----
}
};
</script>

View File

@@ -0,0 +1,850 @@
<template>
<div v-if="value != null" class="mt-8">
<v-row>
<v-col cols="12">
<v-menu offset-y max-width="600px">
<template v-slot:activator="{ on, attrs }">
<div class="text-h6">
<v-icon large :color="hasData ? 'primary' : null" class="mr-2"
>$ayiFan</v-icon
>
{{ $ay.t("WorkOrderItemUnitList") }}
<v-btn v-if="!parentDeleted" large icon v-bind="attrs" v-on="on">
<v-icon small color="primary">$ayiEllipsisV</v-icon>
</v-btn>
</div>
</template>
<v-list>
<v-list-item v-if="canAdd" @click="newItem">
<v-list-item-icon>
<v-icon>$ayiPlus</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ $ay.t("New") }}</v-list-item-title>
</v-list-item>
<v-list-item v-if="canAdd" @click="showBulkUnitsDialog()">
<v-list-item-icon>
<v-icon>$ayiFan</v-icon>
</v-list-item-icon>
<v-list-item-title>{{
$ay.t("AddMultipleUnits")
}}</v-list-item-title>
</v-list-item>
<v-list-item v-if="canDelete && isDeleted" @click="unDeleteItem">
<v-list-item-icon>
<v-icon>$ayiTrashRestoreAlt</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ $ay.t("Undelete") }}</v-list-item-title>
</v-list-item>
<v-list-item v-if="canDelete && !isDeleted" @click="deleteItem">
<v-list-item-icon>
<v-icon>$ayiTrashAlt</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ $ay.t("SoftDelete") }}</v-list-item-title>
</v-list-item>
<v-list-item v-if="canDeleteAll" @click="deleteAllItem">
<v-list-item-icon>
<v-icon>$ayiDumpster</v-icon>
</v-list-item-icon>
<v-list-item-title>{{
$ay.t("SoftDeleteAll")
}}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-col>
<template v-if="hasData">
<!-- ############################################################### -->
<v-col cols="12" class="mb-10">
<v-data-table
:headers="headerList"
:items="itemList"
item-key="index"
v-model="selectedRow"
class="elevation-1"
disable-pagination
disable-filtering
disable-sort
hide-default-footer
data-cy="unitsTable"
dense
:item-class="itemRowClasses"
@click:row="handleRowClick"
:show-select="$vuetify.breakpoint.xs"
single-select
>
</v-data-table>
</v-col>
</template>
<template v-if="hasData && hasSelection">
<div ref="unittopform"></div>
<v-btn
v-if="canDelete && isDeleted"
large
@click="unDeleteItem"
color="primary"
>{{ $ay.t("Undelete")
}}<v-icon right large>$ayiTrashRestoreAlt</v-icon></v-btn
>
<v-col
v-if="form().showMe(this, 'WorkOrderItemUnit')"
cols="12"
sm="6"
lg="4"
xl="3"
>
<gz-pick-list
:aya-type="$ay.ayt().Unit"
:variant="'customerid:' + value.customerId"
:show-edit-icon="!value.userIsRestrictedType"
v-model="
value.items[activeWoItemIndex].units[activeItemIndex].unitId
"
:readonly="
formState.readOnly || isDeleted || value.userIsRestrictedType
"
:disabled="isDeleted"
:label="$ay.t('Unit')"
:ref="
`Items[${activeWoItemIndex}].units[${activeItemIndex}].unitId`
"
data-cy="units.unitId"
:rules="[
form().required(
this,
`Items[${activeWoItemIndex}].units[${activeItemIndex}].unitId`
)
]"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].units[${activeItemIndex}].unitId`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].units[${activeItemIndex}].unitId`
)
"
@update:name="unitChange"
></gz-pick-list>
</v-col>
<v-col
v-if="
form().showMe(this, 'UnitWarrantyInfo') &&
!value.userIsRestrictedType
"
cols="12"
sm="6"
lg="4"
xl="3"
>
<v-card>
<v-card-title>{{ $ay.t("UnitWarrantyInfo") }}</v-card-title>
<v-card-text
v-html="
value.items[activeWoItemIndex].units[activeItemIndex]
.warrantyViz
"
>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="primary" text @click="getWarrantyInfo">{{
$ay.t("Search")
}}</v-btn>
</v-card-actions>
</v-card>
</v-col>
<v-col
v-if="
form().showMe(this, 'WorkOrderItemUnitNotes') &&
!value.userIsRestrictedType
"
cols="12"
>
<v-textarea
v-model="
value.items[activeWoItemIndex].units[activeItemIndex].notes
"
:readonly="formState.readOnly"
:disabled="isDeleted"
:label="$ay.t('WorkOrderItemUnitNotes')"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].units[${activeItemIndex}].notes`
)
"
:ref="`Items[${activeWoItemIndex}].units[${activeItemIndex}].notes`"
data-cy="unitUnitNotes"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].units[${activeItemIndex}].notes`
)
"
auto-grow
></v-textarea>
</v-col>
<v-col
v-if="
form().showMe(this, 'WorkOrderItemUnitTags') &&
!value.userIsRestrictedType
"
cols="12"
>
<gz-tag-picker
v-model="value.items[activeWoItemIndex].units[activeItemIndex].tags"
:readonly="formState.readOnly"
data-cy="unitTags"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].units[${activeItemIndex}].tags`
)
"
:ref="`Items[${activeWoItemIndex}].units[${activeItemIndex}].tags`"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].units[${activeItemIndex}].tags`
)
"
></gz-tag-picker>
</v-col>
<v-col v-if="!value.userIsRestrictedType" cols="12">
<gz-custom-fields
v-model="
value.items[activeWoItemIndex].units[activeItemIndex].customFields
"
:form-key="formCustomTemplateKey"
:readonly="formState.readOnly"
:parent-v-m="this"
key-start-with="WorkOrderItemUnitCustom"
:ref="
`Items[${activeWoItemIndex}].units[${activeItemIndex}].customFields`
"
data-cy="unitCustomFields"
:error-messages="
form().serverErrors(
this,
`Items[${activeWoItemIndex}].units[${activeItemIndex}].customFields`
)
"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].units[${activeItemIndex}].customFields`
)
"
></gz-custom-fields>
</v-col>
<v-col
v-if="
form().showMe(this, 'WorkOrderItemUnitWiki') &&
!value.userIsRestrictedType
"
cols="12"
>
<gz-wiki
:aya-type="$ay.ayt().WorkOrderItem"
:aya-id="value.id"
:ref="`Items[${activeWoItemIndex}].units[${activeItemIndex}].wiki`"
v-model="value.items[activeWoItemIndex].units[activeItemIndex].wiki"
:readonly="formState.readOnly"
@input="
fieldValueChanged(
`Items[${activeWoItemIndex}].units[${activeItemIndex}].wiki`
)
"
></gz-wiki
></v-col>
<v-col
v-if="
form().showMe(this, 'WorkOrderItemUnitAttachments') &&
value.id &&
!value.userIsRestrictedType
"
cols="12"
>
<gz-attachments
:readonly="formState.readOnly"
:aya-type="$ay.ayt().WorkOrderItemUnit"
:aya-id="value.items[activeWoItemIndex].units[activeItemIndex].id"
></gz-attachments
></v-col>
</template>
</v-row>
<!-- ################################################################################-->
<!-- ########################## BULK ADD UNITS FORM ###############################-->
<!-- ################################################################################-->
<template>
<v-row justify="center">
<v-dialog persistent max-width="600px" v-model="bulkUnitsDialog">
<v-card>
<v-card-title>{{ $ay.t("AddMultipleUnits") }}</v-card-title>
<v-card-text>
<gz-tag-picker v-model="selectedBulkUnitTags"></gz-tag-picker>
<gz-pick-list
:aya-type="$ay.ayt().Customer"
:show-edit-icon="false"
v-model="selectedBulkUnitCustomer"
:label="$ay.t('Customer')"
:can-clear="true"
></gz-pick-list>
<v-data-table
dense
v-model="selectedBulkUnits"
:headers="bulkUnitTableHeaders"
:items="availableBulkUnits"
class="my-10"
:disable-filtering="true"
hide-default-footer
:no-data-text="$ay.t('NoData')"
show-select
item-key="unitId"
>
</v-data-table>
</v-card-text>
<v-card-actions>
<v-btn text @click="bulkUnitsDialog = false" color="primary">{{
$ay.t("Cancel")
}}</v-btn>
<v-spacer></v-spacer>
<v-btn
:disabled="selectedBulkUnitTags.length == 0"
color="primary"
text
@click="bulkUnitsSearch()"
>{{ $ay.t("Search") }}</v-btn
>
<v-btn
:disabled="selectedBulkUnits.length == 0"
color="primary"
text
@click="addSelectedBulkUnits()"
>{{ $ay.t("Add") }}</v-btn
>
</v-card-actions>
</v-card>
</v-dialog>
</v-row>
</template>
</div>
</template>
<script>
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/* XXXeslint-disable */
////////////////////////////////////////////////////////////////////////////////////////////////////////////
export default {
created() {
this.setDefaultView();
},
data() {
return {
activeItemIndex: null,
selectedRow: [],
bulkUnitsDialog: false,
selectedBulkUnitCustomer: this.value.customerId,
availableBulkUnits: [],
selectedBulkUnits: [],
selectedBulkUnitTags: [],
bulkUnitTableHeaders: []
};
},
props: {
value: {
default: null,
type: Object
},
pvm: {
default: null,
type: Object
},
activeWoItemIndex: {
default: null,
type: Number
},
gotoIndex: {
default: null,
type: Number
},
addNew: {
default: null,
type: Number
}
},
watch: {
activeWoItemIndex(val, oldVal) {
if (val != oldVal) {
this.setDefaultView();
}
},
gotoIndex(val, oldVal) {
if (val != oldVal) {
let gotoIndex = val;
if (val < 0) {
//it's a create request
gotoIndex = this.newItem();
}
this.selectedRow = [{ index: gotoIndex }];
this.activeItemIndex = gotoIndex;
this.$nextTick(() => {
const el = this.$refs.unittopform;
if (el) {
el.scrollIntoView({ behavior: "smooth" });
}
});
}
}
},
methods: {
async updateContractIfApplicable() {
const id = this.value.items[this.activeWoItemIndex].units[
this.activeItemIndex
].unitId;
if (!id || id == 0) {
return;
}
let res = await window.$gz.api.get(`unit/active-contract/${id}`);
if (res.error) {
window.$gz.eventBus.$emit(
"notify-warning",
window.$gz.errorHandler.errorToString(res, this)
);
} else {
if (res.data.id == 0) {
//no contract, just bail
return;
}
//has contract, if it differs from main work order contract then offer to set it
if (res.data.id == this.value.contractId) {
//same contract, just bail
//yes the date could not be different but we're not going to pick that nit, they can just unset and set if it matters
return;
}
//Prompt user to use new contract
const prompt = this.$ay
.t("ApplyUnitContract")
.replace("{0}", res.data.name);
let dialogResult = await window.$gz.dialog.confirmGenericPreTranslated(
prompt,
"question"
);
if (dialogResult == false) {
return;
} else {
this.value.contractId = res.data.id;
this.value.isDirty = true;
this.pvm.formState.dirty = true;
this.$emit("change");
}
}
},
async getWarrantyInfo() {
this.warrantyInfo = null;
const id = this.value.items[this.activeWoItemIndex].units[
this.activeItemIndex
].unitId;
if (!id || id == 0) {
return;
}
let res = await window.$gz.api.get(`unit/service-warranty-info/${id}`);
if (res.error) {
window.$gz.eventBus.$emit(
"notify-warning",
window.$gz.errorHandler.errorToString(res, this)
);
} else {
/* {
"recentWorkOrders":[{"serial":10,"id":10,"serviceDate":"2021-01-19T22:00:00Z"},{"serial":8,"id":8,"serviceDate":"2021-04-08T22:00:00Z"},{"serial":7,"id":7,"serviceDate":"2021-04-18T20:00:00Z"}],
"purchaseDate":"2019-03-12T09:37:52.930923Z",
"purchasedFromVendor":null,
"purchaseFromVendorId":null,
"purchaseReceiptNumber":"139736",
"lifeTimeWarranty":false,
"warrantyExpiryDate":"2019-04-12T09:37:52.930923Z",
"warrantyTerms":"Shipping parts and service"}
"UnitPurchasedDate": "Purchased Date",
"UnitPurchaseFromID": "Purchased From",
"UnitReceipt": "Receipt Number",
*/
const r = res.data;
let WarrantyInfo = `${this.$ay.t("Warranty")}: `;
let WarrantyExpiryInfo = "-";
if (r.lifeTimeWarranty) {
WarrantyExpiryInfo = this.$ay.t("UnitModelLifeTimeWarranty");
} else {
if (r.warrantyExpiryDate) {
WarrantyExpiryInfo = `${this.$ay.t(
"WarrantyExpires"
)}: ${window.$gz.locale.utcDateToShortDateAndTimeLocalized(
r.warrantyExpiryDate,
this.pvm.timeZoneName,
this.pvm.languageName,
this.pvm.hour12
)}`;
}
}
WarrantyInfo += WarrantyExpiryInfo;
let PurchasedFrom = "-";
if (r.purchaseFromVendorId) {
PurchasedFrom = `<a href="/vendors/${r.purchaseFromVendorId}"> ${r.purchasedFromVendor}</a>`;
}
let PurchasedDate = "-";
if (r.purchaseDate) {
PurchasedDate = window.$gz.locale.utcDateToShortDateAndTimeLocalized(
r.purchaseDate,
this.pvm.timeZoneName,
this.pvm.languageName,
this.pvm.hour12
);
}
let PurchaseInfo = `${this.$ay.t(
"UnitPurchaseFromID"
)}: ${PurchasedFrom}<br/>${this.$ay.t(
"UnitPurchasedDate"
)}: ${PurchasedDate}<br/>${this.$ay.t(
"UnitReceipt"
)}: ${r.purchaseReceiptNumber ?? "-"}`;
let RecentWorkOrderList = "";
if (r.recentWorkOrders.length > 0) {
RecentWorkOrderList += "<br/>";
r.recentWorkOrders.forEach(x => {
RecentWorkOrderList += `<a href="/svc-workorders/${x.id}"> ${
x.serial
}<span class='ml-5'>${window.$gz.locale.utcDateToShortDateAndTimeLocalized(
x.serviceDate,
this.pvm.timeZoneName,
this.pvm.languageName,
this.pvm.hour12
)}</span></a><br/>`;
});
} else {
RecentWorkOrderList = "-";
}
let RecentWorkOrderInfo = `${this.$ay.t(
"RecentWorkOrders"
)}:${RecentWorkOrderList}`;
let d = `<div>${WarrantyInfo}<br/>${PurchaseInfo}<br/>${RecentWorkOrderInfo}</div>`;
this.value.items[this.activeWoItemIndex].units[
this.activeItemIndex
].warrantyViz = d;
}
},
showBulkUnitsDialog() {
if (this.bulkUnitTableHeaders.length == 0) {
//init bulk table headers
this.bulkUnitTableHeaders = [
{ text: this.$ay.t("Customer"), value: "customerName" },
{ text: this.$ay.t("Unit"), value: "unitSerial" }
];
}
this.bulkUnitsDialog = true;
},
async bulkUnitsSearch() {
this.selectedBulkUnits.splice(0);
this.availableBulkUnits.splice(0);
let res = await window.$gz.api.post(`unit/bulk-add-selection-list`, {
tags: this.selectedBulkUnitTags,
restrictToCustomerId: this.selectedBulkUnitCustomer
});
if (res.error) {
window.$gz.eventBus.$emit(
"notify-warning",
window.$gz.errorHandler.errorToString(res, this)
);
} else {
this.availableBulkUnits = res.data;
}
},
addSelectedBulkUnits() {
if (this.selectedBulkUnits.length > 0) {
let newIndex = this.value.items[this.activeWoItemIndex].units.length;
this.selectedBulkUnits.forEach(z => {
newIndex++;
this.value.items[this.activeWoItemIndex].units.push({
id: 0,
concurrency: 0,
wiki: null,
customFields: "{}",
tags: [],
notes: null,
unitId: z.unitId, //zero to break rule on new
isDirty: true,
quoteItemId: this.value.items[this.activeWoItemIndex].id,
uid: Date.now(),
unitViz: z.unitSerial,
warrantyViz: null
});
});
this.$emit("change");
this.selectedRow = [{ index: newIndex }];
this.activeItemIndex = newIndex;
}
this.bulkUnitsDialog = false;
},
async unitChange(newName) {
this.value.items[this.activeWoItemIndex].units[
this.activeItemIndex
].unitViz = newName;
this.value.items[this.activeWoItemIndex].units[
this.activeItemIndex
].warrantyViz = null;
await this.updateContractIfApplicable();
},
newItem() {
let newIndex = this.value.items[this.activeWoItemIndex].units.length;
// {
// "0": {
// "id": 53,
// "concurrency": 9586762,
// "notes": "Aut modi molestias molestiae ipsa id.",
// "wiki": null,
// "customFields": null,
// "tags": [],
// "unitId": 4,
// "unitViz": "83429560",
// "quoteItemId": 22
// }
// }
this.value.items[this.activeWoItemIndex].units.push({
id: 0,
concurrency: 0,
wiki: null,
customFields: "{}",
tags: [],
notes: null,
unitId: 0, //zero to break rule on new
isDirty: true,
quoteItemId: this.value.items[this.activeWoItemIndex].id,
uid: Date.now(),
unitViz: null,
warrantyViz: null
});
this.$emit("change");
this.selectedRow = [{ index: newIndex }];
this.activeItemIndex = newIndex;
//trigger rule breaking / validation
this.$nextTick(() => {
this.value.items[this.activeWoItemIndex].units[
this.activeItemIndex
].unitId = null;
this.fieldValueChanged(
`Items[${this.activeWoItemIndex}].units[${this.activeItemIndex}].unitId`
);
});
return newIndex; //for create new on goto
},
unDeleteItem() {
this.value.items[this.activeWoItemIndex].units[
this.activeItemIndex
].deleted = false;
this.setDefaultView();
},
deleteItem() {
this.value.items[this.activeWoItemIndex].units[
this.activeItemIndex
].deleted = true;
this.setDefaultView();
this.$emit("change");
},
deleteAllItem() {
this.value.items[this.activeWoItemIndex].units.forEach(
z => (z.deleted = true)
);
this.setDefaultView();
this.$emit("change");
},
setDefaultView: function() {
//if only one record left then display it otherwise just let the datatable show what the user can click on
if (this.value.items[this.activeWoItemIndex].units.length == 1) {
this.selectedRow = [{ index: 0 }];
this.activeItemIndex = 0;
} else {
this.selectedRow = [];
this.activeItemIndex = null; //select nothing in essence resetting a child selects and this one too clearing form
}
},
handleRowClick: function(item) {
this.activeItemIndex = item.index;
this.selectedRow = [{ index: item.index }];
},
form() {
return window.$gz.form;
},
fieldValueChanged(ref) {
if (!this.formState.loading && !this.formState.readonly) {
//flag this record dirty so it gets picked up by save
this.value.items[this.activeWoItemIndex].units[
this.activeItemIndex
].isDirty = true;
window.$gz.form.fieldValueChanged(this.pvm, ref);
// //set viz if applicable
// if(ref==""){
// let selectedPartWarehouse = vm.$refs.partWarehouseId.getFullSelectionValue();
// }
}
},
itemRowClasses: function(item) {
let ret = "";
const isDeleted =
this.value.items[this.activeWoItemIndex].units[item.index].deleted ===
true;
const hasError = this.form().childRowHasError(
this,
`Items[${this.activeWoItemIndex}].Units[${item.index}].`
);
if (isDeleted) {
ret += this.form().tableRowDeletedClass();
}
if (hasError) {
ret += this.form().tableRowErrorClass();
}
return ret;
}
},
computed: {
isDeleted: function() {
if (
this.value.items[this.activeWoItemIndex].units[this.activeItemIndex] ==
null
) {
this.setDefaultView();
return true;
}
return (
this.value.items[this.activeWoItemIndex].units[this.activeItemIndex]
.deleted === true
);
},
parentDeleted: function() {
return this.value.items[this.activeWoItemIndex].deleted === true;
},
headerList: function() {
/*
If the column is a text, left-align it
If the column is a number or number + unit, (or date) right-align it (like excel)
*/
let headers = [];
if (this.form().showMe(this, "WorkOrderItemUnit")) {
headers.push({
text: this.$ay.t("Unit"),
align: "left",
value: "unitViz"
});
}
if (this.form().showMe(this, "UnitModelModelNumber")) {
headers.push({
text: this.$ay.t("UnitModelModelNumber"),
align: "left",
value: "unitModelModelNumberViz"
});
}
if (this.form().showMe(this, "UnitModelVendorID")) {
headers.push({
text: this.$ay.t("UnitModelVendorID"),
align: "left",
value: "unitModelVendorViz"
});
}
if (this.form().showMe(this, "UnitModelName")) {
headers.push({
text: this.$ay.t("UnitModelName"),
align: "left",
value: "unitModelNameViz"
});
}
if (this.form().showMe(this, "UnitDescription")) {
headers.push({
text: this.$ay.t("UnitDescription"),
align: "left",
value: "unitDescriptionViz"
});
}
if (
this.form().showMe(this, "WorkOrderItemUnitNotes") &&
!this.value.userIsRestrictedType
) {
headers.push({
text: this.$ay.t("WorkOrderItemUnitNotes"),
align: "left",
value: "notes"
});
}
return headers;
},
itemList: function() {
return this.value.items[this.activeWoItemIndex].units.map((x, i) => {
return {
index: i,
id: x.id,
unitViz: x.unitViz,
unitModelModelNumberViz: x.unitModelModelNumberViz,
unitModelVendorViz: x.unitModelVendorViz,
unitModelNameViz: x.unitModelNameViz,
unitDescriptionViz: x.unitDescriptionViz,
notes: window.$gz.util.truncateString(
x.notes,
this.pvm.maxTableNotesLength
)
};
});
},
formState: function() {
return this.pvm.formState;
},
formCustomTemplateKey: function() {
return this.pvm.formCustomTemplateKey;
},
hasData: function() {
return this.value.items[this.activeWoItemIndex].units.length > 0;
},
hasSelection: function() {
return this.activeItemIndex != null;
},
canAdd: function() {
return this.pvm.rights.change && !this.value.userIsRestrictedType;
},
canDelete: function() {
return (
this.activeItemIndex != null &&
this.canDeleteAll &&
!this.value.userIsRestrictedType
);
},
canDeleteAll: function() {
return this.pvm.rights.change && !this.value.userIsRestrictedType;
}
}
};
</script>

View File

@@ -888,7 +888,7 @@ export default {
);
this.pvm.washWorkOrderItem(wi);
wi.workOrderId = this.value.id;
wi.quoteId = this.value.id;
wi.sequence = newIndex + 1;
this.value.items.push(wi);
this.$emit("change");
@@ -931,7 +931,7 @@ export default {
wiki: null,
customFields: "{}",
tags: [],
workOrderId: this.value.id,
quoteId: this.value.id,
techNotes: null,
workorderItemStatusId: null,
workorderItemPriorityId: null,

View File

@@ -0,0 +1,299 @@
<template>
<div>
<div class="mb-n2 ml-10">
<span class="text-caption">{{ $ay.t("QuoteQuoteStatusType") }}</span>
</div>
<template>
<div class="mb-6 mb-sm-0">
<v-btn icon class="ml-n1 mr-2" @click="openDialog = true">
<v-icon>{{ openIcon() }}</v-icon>
</v-btn>
<span class="text-h6" @click="openDialog = true">{{
pvm.currentState.name
}}</span>
<v-icon :color="pvm.currentState.color" class="ml-4">$ayiFlag</v-icon>
<v-icon color="primary" v-if="pvm.currentState.locked" class="ml-4"
>$ayiLock</v-icon
>
<v-icon color="primary" v-if="pvm.currentState.completed" class="ml-4"
>$ayiCheckCircle</v-icon
>
</div>
</template>
<v-row justify="center">
<v-dialog v-model="openDialog" max-width="600px">
<v-card>
<v-card-title>
<span class="text-h5">{{ $ay.t("QuoteQuoteStatusType") }}</span>
</v-card-title>
<v-card-text>
<template v-if="$vuetify.breakpoint.smAndUp">
<!-- WIDE VIEW -->
<div v-for="item in stateDisplayList" :key="item.id">
<span>{{ item.timeStamp }}</span>
<span class="ml-3">{{ item.user }}</span>
<span class="font-weight-bold ml-3">{{ item.name }}</span>
<v-icon small :color="item.color" class="ml-4">$ayiFlag</v-icon>
<v-icon small color="primary" v-if="item.locked" class="ml-4"
>$ayiLock</v-icon
>
<v-icon small color="primary" v-if="item.completed" class="ml-4"
>$ayiCheckCircle</v-icon
>
</div>
</template>
<template v-else>
<!-- NARROW VIEW -->
<div v-for="item in stateDisplayList" :key="item.id">
<span>{{ item.timeStamp }}&nbsp;</span>
<span>{{ item.user }}</span>
<div class="mb-2">
<span class="font-weight-bold">{{ item.name }}</span>
<v-icon small :color="item.color" class="ml-4"
>$ayiFlag</v-icon
>
<v-icon small color="primary" v-if="item.locked" class="ml-4"
>$ayiLock</v-icon
>
<v-icon
small
color="primary"
v-if="item.completed"
class="ml-4"
>$ayiCheckCircle</v-icon
>
</div>
</div>
</template>
<!-- append-outer-icon="$ayiPlus"
@click:append-outer="addState()" -->
<template v-if="canAdd">
<div class="mt-8">
<v-autocomplete
v-model="selectedStatus"
:items="pvm.selectLists.allowedquotestatus"
item-text="name"
item-value="id"
dense
:label="$ay.t('NewStatus')"
prepend-icon="$ayiEdit"
@click:prepend="handleEditStateClick()"
>
<template v-slot:item="data">
<v-list-item-avatar>
<v-icon :color="data.item.color">$ayiFlag</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title
><span class="text-subtitle-2">{{
data.item.name
}}</span
><v-icon
small
color="disabled"
class="ml-2"
v-if="data.item.locked"
>$ayiLock</v-icon
>
<v-icon
color="disabled"
class="ml-1"
small
v-if="data.item.completed"
>$ayiCheckCircle</v-icon
></v-list-item-title
>
<v-list-item-subtitle>
{{ data.item.notes }}</v-list-item-subtitle
>
</v-list-item-content>
<v-list-item-action> </v-list-item-action>
</template>
</v-autocomplete>
</div>
</template>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text @click="cancelDialog()">{{
$ay.t("Cancel")
}}</v-btn>
<v-btn
color="blue darken-1"
:disabled="selectedStatus == null"
text
@click="save()"
>{{ $ay.t("OK") }}</v-btn
>
</v-card-actions>
</v-card>
</v-dialog>
</v-row>
</div>
</template>
<script>
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/* XXXeslint-disable */
////////////////////////////////////////////////////////////////////////////////////////////////////////////
export default {
data() {
return {
selectedStatus: null,
openDialog: false
};
},
props: {
value: {
default: null,
type: Object
},
pvm: {
default: null,
type: Object
},
allStates: {
default: null,
type: Array
},
allowedStates: {
default: null,
type: Array
},
formKey: { type: String, default: "" }, //used to grab template from store
readonly: Boolean,
disabled: Boolean
},
methods: {
addState() {
if (this.selectedStatus != null) {
//first remove any other non saved states in collection, no need to send them to the server if there was multiple state changes since last save
this.value.states = this.value.states.filter(
z => z.concurrency != null
);
//is it a different state?
if (this.selectedStatus == this.pvm.currentState.id) {
return;
}
//push in new state
this.value.states.push({
quoteId: this.value.id,
quoteStatusId: this.selectedStatus,
userId: window.$gz.store.state.userId,
userViz: window.$gz.store.state.userName,
created: window.$gz.locale.nowUTC8601String()
});
this.selectedStatus = null;
this.pvm.formState.dirty = true;
}
},
getStateForDisplay(state) {
let ret = {
id: Date.now,
name: "??",
color: "#ffffff",
locked: false,
completed: false,
timeStamp: "??",
user: "??"
};
const s = this.allStates.find(z => z.id == state.quoteStatusId);
if (s == null) {
return ret;
}
//make for display
ret.id = state.id;
ret.name = s.name;
ret.color = s.color;
ret.locked = s.locked;
ret.completed = s.completed;
ret.user = state.userViz;
ret.timeStamp = window.$gz.locale.utcDateToShortDateAndTimeLocalized(
state.created,
this.pvm.timeZoneName,
this.pvm.languageName,
this.pvm.hour12
);
return ret;
},
form() {
return window.$gz.form;
},
openIcon: function() {
if (this.canAdd) {
return "$ayiEdit";
}
return "$ayiHistory";
},
handleEditStateClick: function() {
window.$gz.eventBus.$emit("openobject", {
type: window.$gz.type.QuoteStatus,
id: this.selectedStatus
});
},
save() {
this.addState();
this.openDialog = false;
},
cancelDialog() {
this.selectedStatus = null;
this.openDialog = false;
},
fieldValueChanged(ref) {
if (!this.pvm.formState.loading && !this.pvm.formState.readonly) {
window.$gz.form.fieldValueChanged(this.pvm, ref);
}
}
},
computed: {
hasState() {
return this.value.states != null && this.value.states.length > 0;
},
stateDisplayList() {
let ret = [];
this.value.states.forEach(z => {
ret.push(this.getStateForDisplay(z));
});
return ret;
},
canAdd: function() {
//first check most obvious disqualifying properties
if (!this.pvm.rights.change) {
return false;
}
//not currently locked, user has rights to do it so allow it
if (!this.value.isLockedAtServer) {
return true;
}
//locked, confirm if user can change it
//if any role then no problem
//ok, only thing left to check is if the current user can unlock this
//get remove roles required for current state
let cs = this.pvm.currentState;
if (cs.removeRoles == null || cs.removeRoles == 0) {
//no state set yet
return true;
}
//ok, need to check the role here against current user roles to see if this is valid
if (window.$gz.role.hasRole(cs.removeRoles)) {
return true;
}
return false;
}
}
};
</script>

View File

@@ -88,7 +88,7 @@ export default {
let wi = this.$route.params.copyItem;
if (wi) {
this.washWorkOrderItem(wi);
wi.workOrderId = vm.obj.id;
wi.quoteId = vm.obj.id;
wi.sequence = vm.obj.items.length + 1;
vm.obj.items.push(wi);
setDirty = true;
@@ -104,7 +104,7 @@ export default {
this.obj.serial = 0;
this.obj.isDirty = true;
vm.obj.items.forEach(z => {
z.workOrderId = 0;
z.quoteId = 0;
this.washWorkOrderItem(z);
});
setDirty = true;
@@ -228,8 +228,8 @@ export default {
hour12: window.$gz.locale.getHour12(),
// resetSelections: false,
selectLists: {
wostatus: [],
allowedwostatus: [],
quotestatus: [],
allowedquotestatus: [],
woItemPriorities: [],
woItemStatus: [],
woItemTaskCompletionTypes: [],
@@ -288,8 +288,8 @@ export default {
//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
const found = this.selectLists.quotestatus.find(
z => z.id == laststate.quoteStatusId
);
if (found) {
return found;
@@ -625,19 +625,19 @@ export default {
wi.expenses.forEach(x => {
x.id = 0;
x.workOrderItemId = 0;
x.quoteItemId = 0;
x.concurrency = undefined;
x.isDirty = true;
});
wi.labors.forEach(x => {
x.id = 0;
x.workOrderItemId = 0;
x.quoteItemId = 0;
x.concurrency = undefined;
x.isDirty = true;
});
wi.loans.forEach(x => {
x.id = 0;
x.workOrderItemId = 0;
x.quoteItemId = 0;
x.concurrency = undefined;
x.isDirty = true;
x.cost = 0;
@@ -645,7 +645,7 @@ export default {
});
wi.parts.forEach(x => {
x.id = 0;
x.workOrderItemId = 0;
x.quoteItemId = 0;
x.concurrency = undefined;
x.isDirty = true;
x.cost = 0;
@@ -654,31 +654,31 @@ export default {
wi.scheduledUsers.forEach(x => {
x.id = 0;
x.workOrderItemId = 0;
x.quoteItemId = 0;
x.concurrency = undefined;
x.isDirty = true;
});
wi.tasks.forEach(x => {
x.id = 0;
x.workOrderItemId = 0;
x.quoteItemId = 0;
x.concurrency = undefined;
x.isDirty = true;
});
wi.travels.forEach(x => {
x.id = 0;
x.workOrderItemId = 0;
x.quoteItemId = 0;
x.concurrency = undefined;
x.isDirty = true;
});
wi.units.forEach(x => {
x.id = 0;
x.workOrderItemId = 0;
x.quoteItemId = 0;
x.concurrency = undefined;
x.isDirty = true;
});
wi.outsideServices.forEach(x => {
x.id = 0;
x.workOrderItemId = 0;
x.quoteItemId = 0;
x.concurrency = undefined;
x.isDirty = true;
});
@@ -732,8 +732,8 @@ async function saveHeader(vm) {
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));
vm.obj.states.forEach(z => (z.quoteId = vm.obj.id));
vm.obj.items.forEach(z => (z.quoteId = vm.obj.id));
}
}
}
@@ -1870,7 +1870,7 @@ async function fetchTranslatedText(vm) {
"WorkOrderInternalReferenceNumber",
"WorkOrderOnsite",
"NewStatus",
"WorkOrderStatus",
"QuoteQuoteStatusType",
"WorkOrderCustom1",
"WorkOrderCustom2",
"WorkOrderCustom3",
@@ -2087,8 +2087,8 @@ async function populateSelectionLists(vm) {
vm.formState.serverError = res.error;
window.$gz.form.setErrorBoxErrors(vm);
} else {
vm.selectLists.wostatus = res.data.all;
vm.selectLists.allowedwostatus = res.data.allowed;
vm.selectLists.quotestatus = res.data.all;
vm.selectLists.allowedquotestatus = res.data.allowed;
}
res = await window.$gz.api.get("work-order-item-status/list");