overhauled workorder module

This commit is contained in:
Luca Haid
2025-10-08 13:18:48 +02:00
parent 200226a4bd
commit 2ce870746b
9 changed files with 314 additions and 50 deletions
@@ -57,10 +57,12 @@ Vue.component('workorder-admin', {
additional-class="btn-link workorder-button" title="Tiefbau benötigt" />
<tt-button v-if="!['completed', 'cancelled'].includes(row.status)" icon="fas fa-ban text-danger" @click="cancelWorkorderModalData = row"
additional-class="btn-link workorder-button" title="Auftrag stornieren" />
<tt-button v-if="row.status === 'completed'" icon="fas fa-euro-sign text-purple" @click="chargeWorkorder(row)"
additional-class="btn-link workorder-button" title="Auftrag verrechnen" />
</div>
</div>
</template>
<template v-slot:additionalinfo="{ row }">
<div v-if="editingAdditionalInfoId === row.id">
<tt-textarea v-model="tempAdditionalInfo" @keydown.esc.native="cancelEdit" rows="3" no-form-group sm ref="editTextarea"/>
@@ -111,8 +113,8 @@ Vue.component('workorder-admin', {
</tt-modal>
<tt-modal v-if="massAssignModalData" :show.sync="massAssignModalData" :title="workordersToAssign.length + ' Aufträge zuweisen'" @submit="massAssignCompanies">
<p>Sollen <strong>{{ workordersToAssign.length }}</strong> Workorder(s) der Firma <strong>{{ massAssignModalData.companyName }}</strong> zugewiesen werden?</p>
<tt-date-picker label="Deadline" v-model="massAssignModalData.deadline" :date-range="false" sm row/>
<p>Sollen <strong>{{ workordersToAssign.length }}</strong> Workorder(s) der Firma <strong>{{ massAssignModalData.companyName }}</strong> zugewiesen werden?</p>
<tt-date-picker label="Deadline" v-model="massAssignModalData.deadline" :date-range="false" sm row/>
</tt-modal>
</tt-card>
@@ -125,7 +127,7 @@ Vue.component('workorder-admin', {
crudConfig: {
...window.TT_CONFIG.CRUD_CONFIG, selectable: false, expandable: true,
customRowClass: (row) => {
if (['completed', 'new', 'cancelled'].includes(row.status)) return 'tt-rml-workorder-irrelevant';
if (['completed', 'new', 'cancelled', 'charged', 'archived'].includes(row.status)) return 'tt-rml-workorder-irrelevant';
if (['correction_requested', 'intervention_required'].includes(row.status)) return 'tt-rml-workorder-high';
const deadlineDate = moment.unix(row.deadlineDate);
if (!deadlineDate.isValid()) return 'tt-rml-workorder-irrelevant';
@@ -134,17 +136,42 @@ Vue.component('workorder-admin', {
if (daysLeft <= 21) return 'tt-rml-workorder-medium';
return 'tt-rml-workorder-ontrack';
},
additionalActions: []
additionalActions: [
{
key: 'charge_workorder',
title: 'Verrechnen',
class: 'fas fa-euro-sign text-purple',
condition: (row) => row.status === 'completed'
}
]
}
}
},
computed: {
companiesForMassAssign() {
if (this.workordersToAssign.length === 0) return [];
return Object.values(this.companiesByTenant)[0] || [];
const rows = this.$refs.table?.$refs.table.rows;
if (!rows) return[];
const firstWorkorder = rows.find(r => r.id === this.workordersToAssign[0]);
if (!firstWorkorder) return [];
return this.companiesByTenant[firstWorkorder.tenantId] || [];
}
},
methods: {
async chargeWorkorder(row) {
if (!confirm(`Soll der Auftrag #${row.id} wirklich als "verrechnet" markiert werden?`)) return;
try {
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/WorkorderAdmin/chargeWorkorder`, { workorderId: row.id });
if (data.success) {
window.notify('success', data.message);
this.$refs.table.$refs.table.refreshTable();
} else {
window.notify('error', data.message || 'Aktion fehlgeschlagen.');
}
} catch (e) {
window.notify('error', 'Ein Netzwerkfehler ist aufgetreten.');
}
},
async acceptDocumentation(workorderId) {
const {data} = await axios.post(`${window.TT_CONFIG.BASE_PATH}/WorkorderAdmin/acceptDocumentation`, { workorderId });
if (data.success) {
@@ -306,4 +333,4 @@ Vue.component('workorder-admin', {
deep: true
}
}
});
});
@@ -61,19 +61,27 @@ Vue.component('workorder-tenant-config', {
</div>
<hr>
<div class="row mt-3">
<div class="col-md-4">
<div class="col-md-6">
<h6>Filter für Auftragserstellung</h6>
<pre v-if="editingId !== config.id"
class="code-block">{{ JSON.stringify(JSON.parse(config.workorderCreationFilters || '{}'), null, 2) }}</pre>
<tt-textarea v-else v-model="editableJsonFilter" rows="6"/>
</div>
<div class="col-md-4">
<div class="col-md-6">
<h6>Filter für aktive Arbeitsaufträge</h6>
<pre v-if="editingId !== config.id"
class="code-block">{{ JSON.stringify(JSON.parse(config.workorderActiveFilters || '{}'), null, 2) }}</pre>
<tt-textarea v-else v-model="editableJsonActiveFilter" rows="6"/>
</div>
</div>
<div class="row mt-3">
<div class="col-md-6">
<h6 class="mb-3">Optionen</h6>
<tt-checkbox label="Dokumentation für Tiefbau erforderlich"
v-model="editableItem.civilEngineeringDocsRequired" sm v-if="editingId === config.id"/>
<p v-else>Tiefbau-Doku: {{ config.civilEngineeringDocsRequired ? 'Ja' : 'Nein' }}</p>
</div>
<div class="col-md-4">
<div class="col-md-6">
<h6>Zugeordnete Firmen</h6>
<ul v-if="companiesByTenantMap[config.addressId] && companiesByTenantMap[config.addressId].length"
class="list-group">
@@ -172,7 +180,7 @@ Vue.component('workorder-tenant-config', {
data() {
return {
loading: true, activeTab: 'configs', configs: [], companies: [], editingId: null, editableItem: {},
editableJsonFilter: '', showModal: false, newItem: {}, tenantToAdd: null,
editableJsonFilter: '', editableJsonActiveFilter: '', showModal: false, newItem: {}, tenantToAdd: null,
addressApiUrl: `${window.TT_CONFIG.BASE_PATH}/WorkorderTenantConfig/addressAutocomplete`,
docTypesConfig: {
fields: {
@@ -234,6 +242,8 @@ Vue.component('workorder-tenant-config', {
this.editableItem.documentationTypes = JSON.parse(this.editableItem.documentationTypes || '[]');
this.editableItem.interventionTypes = JSON.parse(this.editableItem.interventionTypes || '[]');
this.editableJsonFilter = JSON.stringify(JSON.parse(this.editableItem.workorderCreationFilters || '{}'), null, 2);
this.editableItem.workorderActiveFilters = this.editableItem.workorderActiveFilters || '{}';
this.editableJsonActiveFilter = JSON.stringify(JSON.parse(this.editableItem.workorderActiveFilters), null, 2);
} else {
try {
this.editableItem.visibleForAddressId = JSON.parse(this.editableItem.visibleForAddressId || '[]');
@@ -246,6 +256,7 @@ Vue.component('workorder-tenant-config', {
this.editingId = null;
this.editableItem = {};
this.editableJsonFilter = '';
this.editableJsonActiveFilter = '';
},
async saveItem() {
const endpoint = this.activeTab === 'configs' ? 'saveTenantConfig' : 'saveCompany';
@@ -258,6 +269,12 @@ Vue.component('workorder-tenant-config', {
} catch (e) {
return window.notify('error', 'Filter für Auftragserstellung ist kein valides JSON.');
}
try {
JSON.parse(this.editableJsonActiveFilter);
payload.workorderActiveFilters = this.editableJsonActiveFilter;
} catch (e) {
return window.notify('error', 'Filter für aktive Arbeitsaufträge ist kein valides JSON.');
}
}
try {
@@ -294,6 +311,7 @@ Vue.component('workorder-tenant-config', {
documentationTypes: [],
interventionTypes: [],
workorderCreationFilters: '{}',
workorderActiveFilters: '{}',
civilEngineeringDocsRequired: 0
}
: {visibleForAddressId: []};