601 lines
27 KiB
JavaScript
601 lines
27 KiB
JavaScript
// RMLWorkorderAdmin.js
|
||
Vue.component('r-m-l-workorder-admin', {
|
||
template: `
|
||
<tt-card>
|
||
<div class="mb-2 d-flex align-items-center" v-if="workordersToAssign.length > 0">
|
||
<span class="mr-3 font-weight-bold">{{ workordersToAssign.length }} Workorder(s) zuweisen:</span>
|
||
<div style="width: 300px;">
|
||
<tt-select
|
||
class="mb-0"
|
||
:options="companiesForMassAssign"
|
||
v-model="massAssignCompanyId"
|
||
@input="massAssignCompanies"
|
||
placeholder="Firma auswählen..."
|
||
sm
|
||
no-form-group
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<tt-table-crud
|
||
ref="table"
|
||
:crud-config="crudConfig"
|
||
>
|
||
<template v-slot:preorderinfo="{ row }">
|
||
<div class="small">
|
||
<div><strong>Kunde:</strong> {{ row.customerCompany || row.customerName }}</div>
|
||
<div>
|
||
<strong>Anschluss:</strong>
|
||
{{ row.street }} {{ row.hausnummer }}
|
||
<template v-if="row.stiege">/{{ row.stiege }}</template>
|
||
<template v-if="row.apartment"> / WE: {{ row.apartment }}</template>
|
||
, {{ row.plz }} {{ row.city }}
|
||
</div>
|
||
<div>
|
||
<strong>OAID:</strong> <span class="text-pink">{{ row.oaid }}</span>
|
||
<tt-button
|
||
icon="fas fa-external-link-alt"
|
||
@click="window.open(window.TT_CONFIG.BASE_PATH + '/Preorder/Index?filter[ucode]=' + row.ucode, '_blank');"
|
||
additional-class="btn-link btn-sm p-0 m-0"
|
||
title="Zur Bestellung"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<template v-slot:status="{ row }">
|
||
<traffic-light :deadline="row.deadlineDate" :status="row.status"/>
|
||
<i :class="getStatusColumn(row.status).icon" :title="getStatusColumn(row.status).text"></i>
|
||
<span class="ml-2">{{ getStatusColumn(row.status).text }}</span>
|
||
<tt-button
|
||
v-if="row.status === 'intervention_required'"
|
||
icon="ml-2 fas fa-check-circle text-success"
|
||
@click="setToProblemSolved(row)"
|
||
additional-class="btn-link btn-sm p-0"
|
||
title="Auftrag auf Problem behoben setzen"
|
||
/>
|
||
</template>
|
||
|
||
<template v-slot:companyname="{ row }">
|
||
<div class="d-flex justify-content-between align-items-center">
|
||
|
||
<div class="flex-grow-1">
|
||
<div v-if="editingWorkorderId === row.id">
|
||
<div v-if="companiesLoading" class="spinner-border spinner-border-sm"></div>
|
||
<tt-select v-else
|
||
:options="companiesByTenant[row.tenantId] || []" :value="row.companyId"
|
||
@input="assignCompany(row, $event)" @blur="editingWorkorderId = null"
|
||
@keydown.esc.native="editingWorkorderId = null" placeholder="Firma zuweisen..."
|
||
sm no-form-group
|
||
/>
|
||
</div>
|
||
<div v-else-if="row.status === 'new'">
|
||
<div v-if="companiesLoading" class="spinner-border spinner-border-sm"></div>
|
||
<tt-select v-else
|
||
:options="companiesByTenant[row.tenantId] || []" :value="row.companyId"
|
||
@input="assignCompany(row, $event)" @focus="getCompaniesForWorkorder(row)"
|
||
placeholder="Firma zuweisen..." sm no-form-group
|
||
/>
|
||
</div>
|
||
<div v-else>
|
||
<span>{{ row.companyName || 'N/A' }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="d-flex align-items-center">
|
||
<tt-button
|
||
v-if="!['completed', 'cancelled', 'new'].includes(row.status)"
|
||
icon="fas fa-edit"
|
||
@click="startCompanyEdit(row)"
|
||
additional-class="btn-link btn-sm p-0 ml-2"
|
||
title="Zuweisung ändern"
|
||
/>
|
||
<tt-button
|
||
v-if="!['completed', 'cancelled'].includes(row.status)"
|
||
icon="fas fa-ban"
|
||
@click="cancelWorkorder(row)"
|
||
additional-class="btn-link btn-sm p-0 ml-2 text-danger"
|
||
title="Auftrag stornieren"
|
||
/>
|
||
<tt-button v-if="!workordersToAssign.includes(row.id)"
|
||
icon="fas fa-plus-circle text-success" @click="addToAssignList(row)"
|
||
additional-class="btn-link btn-sm p-0 ml-2" title="Zur Zuweisungsliste hinzufügen"
|
||
/>
|
||
<tt-button v-if="workordersToAssign.includes(row.id)"
|
||
icon="fas fa-minus-circle text-danger" @click="removeFromAssignList(row)"
|
||
additional-class="btn-link btn-sm p-0 ml-2" title="Von Zuweisungsliste entfernen"
|
||
/>
|
||
</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"
|
||
/>
|
||
<div class="mt-2 d-flex justify-content-end">
|
||
<tt-button
|
||
text="Abbrechen"
|
||
@click="cancelEdit"
|
||
sm
|
||
additional-class="btn-secondary mr-2"
|
||
/>
|
||
<tt-button
|
||
text="Speichern"
|
||
@click="updateAdditionalInfo(row, tempAdditionalInfo)"
|
||
sm
|
||
additional-class="btn-success"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div v-else class="d-flex align-items-start">
|
||
<span
|
||
style="white-space: pre-wrap; max-width: 250px; display: inline-block;">{{ row.additionalInfo || '-' }}</span>
|
||
<tt-button
|
||
icon="fas fa-edit" @click="startAdditionalInfoEdit(row)"
|
||
additional-class="btn-link btn-sm p-0 ml-2" title="Zusatz-Info bearbeiten"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<template v-slot:deadlinedate="{ row }">
|
||
<div v-if="editingDeadlineId === row.id">
|
||
<tt-date-picker
|
||
:value="row.deadlineDate" :date-range="false"
|
||
@input="updateDeadline(row, $event)" @blur="editingDeadlineId = null"
|
||
sm no-form-group
|
||
/>
|
||
</div>
|
||
<div v-else class="d-flex align-items-center">
|
||
<span>{{ formatDate(row.deadlineDate) }}</span>
|
||
<tt-button icon="fas fa-edit" @click="editingDeadlineId = row.id"
|
||
additional-class="btn-link btn-sm p-0 ml-2" title="Deadline ändern"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<template v-slot:appointmentdate="{ row }">
|
||
{{ formatDate(row.appointmentDate, true) }}
|
||
</template>
|
||
|
||
<template v-slot:expandedRow="{ row }">
|
||
<rml-documentation-viewer-admin
|
||
:workorder-id="row.id"
|
||
@workorder-updated="$refs.table.$refs.table.refreshTable()"
|
||
@accept-documentation="acceptDocumentation"
|
||
/>
|
||
</template>
|
||
|
||
</tt-table-crud>
|
||
</tt-card>
|
||
`,
|
||
data() {
|
||
return {
|
||
window,
|
||
workordersToAssign: [],
|
||
editingWorkorderId: null,
|
||
editingDeadlineId: null,
|
||
editingAdditionalInfoId: null,
|
||
tempAdditionalInfo: '',
|
||
companiesByTenant: {},
|
||
companiesLoading: false,
|
||
massAssignCompanyId: null,
|
||
massAssignLoading: false,
|
||
companiesForMassAssign: [],
|
||
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 (['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';
|
||
const daysLeft = deadlineDate.diff(moment(), 'days');
|
||
if (daysLeft <= 7) return 'tt-rml-workorder-urgent';
|
||
if (daysLeft <= 21) return 'tt-rml-workorder-medium';
|
||
return 'tt-rml-workorder-ontrack';
|
||
},
|
||
additionalActions: []
|
||
}
|
||
}
|
||
},
|
||
methods: {
|
||
addToAssignList(row) {
|
||
if (!this.workordersToAssign.includes(row.id)) this.workordersToAssign.push(row.id);
|
||
},
|
||
removeFromAssignList(row) {
|
||
this.workordersToAssign = this.workordersToAssign.filter(id => id !== row.id);
|
||
},
|
||
getStatusColumn(status) {
|
||
const column = this.crudConfig.columns.find(c => c.key === 'status');
|
||
return column.table.filterOptions.find(opt => opt.value === status) || {};
|
||
},
|
||
formatDate(timestamp, withTime = false) {
|
||
if (!timestamp) return '–';
|
||
return window.moment.unix(timestamp).format(withTime ? 'DD.MM.YYYY HH:mm' : 'DD.MM.YYYY');
|
||
},
|
||
async getCompaniesForWorkorder(workorder) {
|
||
if (!workorder.tenantId || this.companiesByTenant[workorder.tenantId]) return;
|
||
this.companiesLoading = true;
|
||
try {
|
||
const {data} = await axios.get(`${window.TT_CONFIG.BASE_PATH}/RMLWorkorderAdmin/getCompanies`, {params: {tenantId: workorder.tenantId}});
|
||
this.$set(this.companiesByTenant, workorder.tenantId, data);
|
||
} catch (e) {
|
||
window.notify('error', 'Firmenliste konnte nicht geladen werden.');
|
||
} finally {
|
||
this.companiesLoading = false;
|
||
}
|
||
},
|
||
async startCompanyEdit(row) {
|
||
await this.getCompaniesForWorkorder(row);
|
||
this.editingWorkorderId = row.id;
|
||
},
|
||
async assignCompany(workorder, companyId) {
|
||
if (!companyId) {
|
||
this.editingWorkorderId = null;
|
||
return;
|
||
}
|
||
try {
|
||
const {data} = await axios.post(`${window.TT_CONFIG.BASE_PATH}/RMLWorkorderAdmin/assignWorkorder`, {
|
||
workorderId: workorder.id,
|
||
companyId: companyId
|
||
});
|
||
if (data.success) {
|
||
window.notify('success', data.message);
|
||
this.$refs.table.$refs.table.refreshTable();
|
||
} else window.notify('error', data.message || 'Ein Fehler ist aufgetreten.');
|
||
} catch (e) {
|
||
window.notify('error', 'Ein Netzwerkfehler ist aufgetreten.');
|
||
} finally {
|
||
this.editingWorkorderId = null;
|
||
}
|
||
},
|
||
async massAssignCompanies(companyId) {
|
||
if (!companyId) return;
|
||
if (!confirm(`${this.workordersToAssign.length} Workorder(s) der ausgewählten Firma zuweisen?`)) {
|
||
this.massAssignCompanyId = null;
|
||
return;
|
||
}
|
||
this.massAssignLoading = true;
|
||
try {
|
||
const {data} = await axios.post(`${window.TT_CONFIG.BASE_PATH}/RMLWorkorderAdmin/massAssignWorkorders`, {
|
||
companyId: companyId,
|
||
workorderIds: this.workordersToAssign
|
||
});
|
||
if (data.success) {
|
||
window.notify('success', data.message);
|
||
this.workordersToAssign = [];
|
||
this.$refs.table.$refs.table.refreshTable();
|
||
} else window.notify('error', data.message || 'Ein Fehler ist aufgetreten.');
|
||
} catch (e) {
|
||
window.notify('error', 'Ein Netzwerkfehler ist aufgetreten.');
|
||
} finally {
|
||
this.massAssignLoading = false;
|
||
this.massAssignCompanyId = null;
|
||
}
|
||
},
|
||
async updateDeadline(workorder, newDate) {
|
||
if (!newDate) {
|
||
this.editingDeadlineId = null;
|
||
return;
|
||
}
|
||
try {
|
||
const {data} = await axios.post(`${window.TT_CONFIG.BASE_PATH}/RMLWorkorderAdmin/updateDeadline`, {
|
||
workorderId: workorder.id,
|
||
deadlineDate: newDate
|
||
});
|
||
if (data.success) {
|
||
window.notify('success', data.message);
|
||
this.$refs.table.$refs.table.refreshTable();
|
||
} else window.notify('error', data.message || 'Ein Fehler ist aufgetreten.');
|
||
} catch (e) {
|
||
window.notify('error', 'Ein Netzwerkfehler ist aufgetreten.');
|
||
} finally {
|
||
this.editingDeadlineId = null;
|
||
}
|
||
},
|
||
async acceptDocumentation(workorderId) {
|
||
if (!confirm('Soll die Dokumentation für diesen Arbeitsauftrag wirklich akzeptiert und der Auftrag abgeschlossen werden?')) return;
|
||
try {
|
||
const {data} = await axios.post(`${window.TT_CONFIG.BASE_PATH}/RMLWorkorderAdmin/acceptDocumentation`, {workorderId});
|
||
if (data.success) {
|
||
window.notify('success', data.message);
|
||
this.$refs.table.$refs.table.refreshTable();
|
||
} else window.notify('error', data.message || 'Ein Fehler ist aufgetreten.');
|
||
} catch (e) {
|
||
window.notify('error', 'Ein Netzwerkfehler ist aufgetreten.');
|
||
}
|
||
},
|
||
async setToProblemSolved(row) {
|
||
const text = prompt('Bitte geben Sie einen kurzen Text für den Journaleintrag ein:', '');
|
||
if (text === null) return;
|
||
if (!text.trim()) {
|
||
window.notify('error', 'Bitte geben Sie einen Text ein.');
|
||
return;
|
||
}
|
||
try {
|
||
const {data} = await axios.post(`${window.TT_CONFIG.BASE_PATH}/RMLWorkorderAdmin/setToProblemSolved`, {
|
||
workorderId: row.id,
|
||
text: text
|
||
});
|
||
if (data.success) {
|
||
window.notify('success', data.message);
|
||
this.$refs.table.$refs.table.refreshTable();
|
||
} else window.notify('error', data.message || 'Ein Fehler ist aufgetreten.');
|
||
} catch (e) {
|
||
window.notify('error', 'Ein Netzwerkfehler ist aufgetreten.');
|
||
}
|
||
},
|
||
startAdditionalInfoEdit(row) {
|
||
this.editingAdditionalInfoId = row.id;
|
||
this.tempAdditionalInfo = row.additionalInfo || '';
|
||
this.$nextTick(() => {
|
||
this.$refs.editTextarea?.$el.querySelector('textarea').focus();
|
||
});
|
||
},
|
||
cancelEdit() {
|
||
this.editingAdditionalInfoId = null;
|
||
this.tempAdditionalInfo = '';
|
||
},
|
||
async updateAdditionalInfo(row, newInfo) {
|
||
if (row.additionalInfo === newInfo) {
|
||
this.cancelEdit();
|
||
return;
|
||
}
|
||
try {
|
||
const {data} = await axios.post(`${window.TT_CONFIG.BASE_PATH}/RMLWorkorderAdmin/updateAdditionalInfo`, {
|
||
workorderId: row.id,
|
||
additionalInfo: newInfo
|
||
});
|
||
if (data.success) {
|
||
window.notify('success', data.message);
|
||
row.additionalInfo = newInfo;
|
||
} else window.notify('error', data.message || 'Update fehlgeschlagen.');
|
||
} catch (e) {
|
||
window.notify('error', 'Netzwerkfehler beim Update.');
|
||
} finally {
|
||
this.cancelEdit();
|
||
}
|
||
},
|
||
async cancelWorkorder(row) {
|
||
const reason = prompt('Bitte geben Sie einen Grund für die Stornierung an (optional):');
|
||
if (reason === null) return;
|
||
try {
|
||
const {data} = await axios.post(`${window.TT_CONFIG.BASE_PATH}/RMLWorkorderAdmin/cancelWorkorder`, {
|
||
workorderId: row.id,
|
||
reason: reason
|
||
});
|
||
if (data.success) {
|
||
window.notify('success', data.message);
|
||
this.$refs.table.$refs.table.refreshTable();
|
||
} else window.notify('error', data.message || 'Stornierung fehlgeschlagen.');
|
||
} catch (e) {
|
||
window.notify('error', 'Ein Netzwerkfehler ist aufgetreten.');
|
||
}
|
||
}
|
||
},
|
||
watch: {
|
||
workordersToAssign: {
|
||
async handler(newVal) {
|
||
if (newVal.length === 0) {
|
||
this.companiesForMassAssign = [];
|
||
return;
|
||
}
|
||
const firstWorkorder = this.$refs.table.$refs.table.rows.find(r => r.id === newVal[0]);
|
||
if (!firstWorkorder) return;
|
||
const firstTenantId = firstWorkorder.tenantId;
|
||
if (!newVal.every(id => {
|
||
const wo = this.$refs.table.$refs.table.rows.find(r => r.id === id);
|
||
return wo && wo.tenantId === firstTenantId;
|
||
})) {
|
||
window.notify('error', 'Massen-Zuweisung nur für Aufträge des gleichen Mandanten möglich.');
|
||
this.workordersToAssign.pop();
|
||
return;
|
||
}
|
||
await this.getCompaniesForWorkorder(firstWorkorder);
|
||
this.companiesForMassAssign = this.companiesByTenant[firstTenantId] || [];
|
||
},
|
||
deep: true
|
||
}
|
||
}
|
||
});
|
||
|
||
Vue.component('traffic-light', {
|
||
props: ['deadline', 'status'],
|
||
computed: {
|
||
lightInfo() {
|
||
if (['completed', 'new', 'cancelled'].includes(this.status)) return {
|
||
color: '#cccccc',
|
||
title: 'Status irrelevant für Dringlichkeit'
|
||
};
|
||
const deadlineDate = moment.unix(this.deadline);
|
||
if (!deadlineDate.isValid()) return {color: '#cccccc', title: 'Keine Deadline gesetzt'};
|
||
if (deadlineDate.isBefore(moment())) return {color: '#dc3545', title: 'Deadline überschritten'};
|
||
const daysLeft = deadlineDate.diff(moment(), 'days');
|
||
if (daysLeft <= 7) return {color: '#dc3545', title: 'Dringend: Weniger als 1 Woche'};
|
||
if (daysLeft <= 21) return {color: '#ffc107', title: 'Mittel: Weniger als 3 Wochen'};
|
||
return {color: '#28a745', title: 'Im Plan: Mehr als 3 Wochen'};
|
||
}
|
||
},
|
||
template: `<span :style="{ color: lightInfo.color, fontSize: '1.2em' }" class="mr-2" :title="lightInfo.title">●</span>`
|
||
});
|
||
|
||
Vue.component('rml-documentation-viewer-admin', {
|
||
props: ['workorderId'],
|
||
template: `
|
||
<div class="p-3 bg-light">
|
||
<div v-if="loading" class="text-center p-5"><i class="fas fa-spinner fa-spin fa-2x"></i></div>
|
||
<div v-else class="row">
|
||
<div class="col-lg-6">
|
||
<tt-file-gallery :files="docs" @selection-changed="selectedDocs = $event" selectable/>
|
||
</div>
|
||
<div class="col-lg-6">
|
||
<div class="card mb-3" v-if="selectedDocs.length > 0">
|
||
<div class="card-header"><h5><i class="fas fa-exclamation-triangle text-danger mr-2"></i>Korrektur
|
||
anfordern</h5></div>
|
||
<div class="card-body">
|
||
<p class="small text-muted">Wählen Sie die zu korrigierenden Dokumente aus der Galerie aus und geben Sie
|
||
einen Grund an.</p>
|
||
<tt-textarea v-model="correctionText" label="Grund für die Korrektur" sm row/>
|
||
<tt-button text="Korrektur anfordern" @click="requestCorrection" :loading="correctionLoading"
|
||
additional-class="btn-danger float-right"/>
|
||
</div>
|
||
</div>
|
||
<div class="card mb-3">
|
||
<div class="card-header"><h5><i class="fas fa-check-circle text-success mr-2"></i>Dokumentation
|
||
akzeptieren</h5></div>
|
||
<div class="card-body">
|
||
<p class="small text-muted">Prüfen Sie, ob alle erforderlichen Dokumente vorhanden und korrekt sind.</p>
|
||
<div v-if="loadingConfig" class="text-center"><i class="fas fa-spinner fa-spin"></i></div>
|
||
<ul v-else class="list-unstyled">
|
||
<li v-for="docType in requiredDocTypes" :key="docType.value"
|
||
class="mb-2 d-flex align-items-center small">
|
||
<i :class="isUploaded(docType.value) ? 'fas fa-check-circle text-success' : 'far fa-circle text-muted'"
|
||
class="fa-fw mr-2"></i>
|
||
<span>{{ docType.text }}</span>
|
||
</li>
|
||
</ul>
|
||
<hr>
|
||
<tt-button text="Dokumentation akzeptieren" @click="$emit('accept-documentation', workorderId)"
|
||
additional-class="btn-success float-right" icon="fas fa-check"/>
|
||
</div>
|
||
</div>
|
||
<div class="card mt-3">
|
||
<div class="card-header"><h5><i class="fas fa-history mr-2"></i>Journal</h5></div>
|
||
<div class="card-body p-0" style="max-height: 200px; overflow-y: auto;">
|
||
<ul v-if="journals.length" class="list-group list-group-flush">
|
||
<li v-for="log in journals" :key="log.id" class="list-group-item small">
|
||
<strong>{{ formatDate(log.create) }} ({{ log.createByName }}):</strong>
|
||
<div class="text-muted" style="white-space: pre-wrap;">{{ log.text }}</div>
|
||
</li>
|
||
</ul>
|
||
<div v-else class="card-body text-muted text-center">Keine Journaleinträge.</div>
|
||
</div>
|
||
<div class="card-footer">
|
||
<tt-textarea v-model="newJournalMessage" placeholder="Ihre Nachricht oder Anmerkung..." rows="2"/>
|
||
<tt-button text="Eintrag speichern" @click="addJournalEntry" :loading="addingJournalEntry"
|
||
additional-class="btn-info btn-sm w-100 mt-2" icon="fas fa-paper-plane"/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>`,
|
||
data: () => ({
|
||
loading: true,
|
||
loadingConfig: true,
|
||
correctionLoading: false,
|
||
docs: [],
|
||
journals: [],
|
||
selectedDocs: [],
|
||
correctionText: '',
|
||
newJournalMessage: '',
|
||
addingJournalEntry: false,
|
||
tenantDocTypes: null
|
||
}),
|
||
computed: {
|
||
requiredDocTypes() {
|
||
if (this.tenantDocTypes) return this.tenantDocTypes;
|
||
return [{value: 'photo_hup_mounted', text: 'Foto vom montierten HÜP'}, {
|
||
value: 'photo_hup_open',
|
||
text: 'Foto von dem offenen HÜP'
|
||
}, {
|
||
value: 'photo_splice_cassette_hup',
|
||
text: 'Foto der Spleißkassette – HÜP'
|
||
}, {
|
||
value: 'photo_splice_cassette_fcp',
|
||
text: 'Foto der Spleißkassette - FCP'
|
||
}, {
|
||
value: 'photo_hup_closed_stickers',
|
||
text: 'Foto vom geschlossenen HÜP mit allen Aufklebern'
|
||
}, {value: 'photo_fcp_labeled', text: 'Foto vom FCP beschriftet'}, {
|
||
value: 'photo_patch_position_osp',
|
||
text: 'Foto der Patch-Position - OSP-Seite'
|
||
}, {
|
||
value: 'photo_patch_position_anb',
|
||
text: 'Foto der Patch-Position - ANB-Seite'
|
||
}, {value: 'measurement_protocol_otdr', text: 'ODTR – Messung (1310nm & 1550nm)'}];
|
||
}
|
||
},
|
||
methods: {
|
||
isUploaded(docType) {
|
||
return this.docs.some(doc => doc.documentType === docType);
|
||
},
|
||
async fetchData() {
|
||
this.loading = true;
|
||
try {
|
||
const {data} = await axios.get(`${window.TT_CONFIG.BASE_PATH}/RMLWorkorderAdmin/getDocumentation`, {params: {workorderId: this.workorderId}});
|
||
this.docs = data.docs;
|
||
this.journals = data.journals;
|
||
} catch (e) {
|
||
window.notify('error', 'Dokumentation konnte nicht geladen werden.');
|
||
} finally {
|
||
this.loading = false;
|
||
}
|
||
},
|
||
async loadTenantConfig() {
|
||
this.loadingConfig = true;
|
||
try {
|
||
const {data} = await axios.get(`${window.TT_CONFIG.BASE_PATH}/RMLWorkorderCompany/getTenantConfig`, {params: {workorderId: this.workorderId}});
|
||
if (data.success) this.tenantDocTypes = data.documentationTypes;
|
||
} catch (e) {
|
||
console.error("Konnte Mandantenkonfiguration nicht laden", e);
|
||
} finally {
|
||
this.loadingConfig = false;
|
||
}
|
||
},
|
||
async requestCorrection() {
|
||
if (!this.correctionText) return window.notify('error', 'Bitte geben Sie einen Grund für die Korrektur an.');
|
||
if (this.selectedDocs.length === 0) return window.notify('error', 'Bitte wählen Sie mindestens ein Dokument für die Korrektur aus.');
|
||
this.correctionLoading = true;
|
||
try {
|
||
const {data} = await axios.post(`${window.TT_CONFIG.BASE_PATH}/RMLWorkorderAdmin/requestCorrection`, {
|
||
workorderId: this.workorderId,
|
||
text: this.correctionText,
|
||
fileIds: this.selectedDocs
|
||
});
|
||
if (data.success) {
|
||
window.notify('success', data.message);
|
||
this.correctionText = '';
|
||
this.selectedDocs = [];
|
||
await this.fetchData();
|
||
this.$emit('workorder-updated');
|
||
} else window.notify('error', data.message);
|
||
} catch (e) {
|
||
window.notify('error', 'Ein Netzwerkfehler ist aufgetreten.');
|
||
}
|
||
this.correctionLoading = false;
|
||
},
|
||
async addJournalEntry() {
|
||
if (!this.newJournalMessage.trim()) return window.notify('error', 'Bitte geben Sie eine Nachricht ein.');
|
||
this.addingJournalEntry = true;
|
||
try {
|
||
const {data} = await axios.post(`${window.TT_CONFIG.BASE_PATH}/RMLWorkorderAdmin/addJournal`, {
|
||
workorderId: this.workorderId,
|
||
text: this.newJournalMessage
|
||
});
|
||
if (data.success) {
|
||
window.notify('success', data.message || 'Journal-Eintrag hinzugefügt.');
|
||
this.newJournalMessage = '';
|
||
this.journals = data.journals;
|
||
} else window.notify('error', data.message || 'Eintrag konnte nicht gespeichert werden.');
|
||
} catch (e) {
|
||
window.notify('error', 'Ein Netzwerkfehler ist aufgetreten.');
|
||
} finally {
|
||
this.addingJournalEntry = false;
|
||
}
|
||
},
|
||
formatDate(timestamp) {
|
||
return window.moment.unix(timestamp).format('DD.MM.YYYY HH:mm');
|
||
},
|
||
},
|
||
async mounted() {
|
||
await this.loadTenantConfig();
|
||
await this.fetchData();
|
||
}
|
||
}); |