improved ipam
This commit is contained in:
@@ -7,7 +7,7 @@ Vue.component('r-m-l-workorder-admin', {
|
||||
<div style="width: 300px;">
|
||||
<tt-select
|
||||
class="mb-0"
|
||||
:options="companies"
|
||||
:options="companiesForMassAssign"
|
||||
v-model="massAssignCompanyId"
|
||||
@input="massAssignCompanies"
|
||||
placeholder="Firma auswählen..."
|
||||
@@ -62,32 +62,35 @@ Vue.component('r-m-l-workorder-admin', {
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="flex-grow-1">
|
||||
<div v-if="editingWorkorderId === row.id">
|
||||
<tt-select
|
||||
:options="companies"
|
||||
:value="row.companyId"
|
||||
@input="assignCompany(row, $event)"
|
||||
@blur="editingWorkorderId = null"
|
||||
@keydown.esc.native="editingWorkorderId = null"
|
||||
placeholder="Firma zuweisen..."
|
||||
sm
|
||||
no-form-group
|
||||
<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'">
|
||||
<tt-select
|
||||
:options="companies"
|
||||
:value="row.companyId"
|
||||
@input="assignCompany(row, $event)"
|
||||
placeholder="Firma zuweisen..."
|
||||
sm
|
||||
no-form-group
|
||||
<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 class="d-flex align-items-center">
|
||||
<span>{{ row.companyName || 'N/A' }}</span>
|
||||
<tt-button
|
||||
icon="fas fa-edit"
|
||||
@click="editingWorkorderId = row.id"
|
||||
@click="startCompanyEdit(row)"
|
||||
additional-class="btn-link btn-sm p-0 ml-2"
|
||||
title="Zuweisung ändern"
|
||||
/>
|
||||
@@ -156,9 +159,11 @@ Vue.component('r-m-l-workorder-admin', {
|
||||
workordersToAssign: [],
|
||||
editingWorkorderId: null,
|
||||
editingDeadlineId: null,
|
||||
companies: [],
|
||||
companiesByTenant: {},
|
||||
companiesLoading: false,
|
||||
massAssignCompanyId: null,
|
||||
massAssignLoading: false,
|
||||
companiesForMassAssign: [],
|
||||
crudConfig: {
|
||||
...window.TT_CONFIG.CRUD_CONFIG,
|
||||
selectable: false,
|
||||
@@ -184,10 +189,6 @@ Vue.component('r-m-l-workorder-admin', {
|
||||
}
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
const response = await axios.get(`${window.TT_CONFIG.BASE_PATH}/RMLWorkorderAdmin/getCompanies`);
|
||||
this.companies = response.data;
|
||||
},
|
||||
methods: {
|
||||
addToAssignList(row) {
|
||||
if (!this.workordersToAssign.includes(row.id)) {
|
||||
@@ -205,6 +206,24 @@ Vue.component('r-m-l-workorder-admin', {
|
||||
if (!timestamp) return '–';
|
||||
return window.moment.unix(timestamp).format('DD.MM.YYYY');
|
||||
},
|
||||
async getCompaniesForWorkorder(workorder) {
|
||||
if (!workorder.tenantId || this.companiesByTenant[workorder.tenantId]) {
|
||||
return;
|
||||
}
|
||||
this.companiesLoading = true;
|
||||
try {
|
||||
const response = await axios.get(`${window.TT_CONFIG.BASE_PATH}/RMLWorkorderAdmin/getCompanies`, { params: { tenantId: workorder.tenantId } });
|
||||
this.$set(this.companiesByTenant, workorder.tenantId, response.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;
|
||||
@@ -320,6 +339,36 @@ Vue.component('r-m-l-workorder-admin', {
|
||||
window.notify('error', 'Ein Netzwerkfehler ist aufgetreten.');
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
workordersToAssign: {
|
||||
async handler(newVal) {
|
||||
if (newVal.length === 0) {
|
||||
this.companiesForMassAssign = [];
|
||||
return;
|
||||
}
|
||||
const firstWorkorderId = newVal[0];
|
||||
const firstWorkorder = this.$refs.table.$refs.table.rows.find(r => r.id === firstWorkorderId);
|
||||
if (!firstWorkorder) return;
|
||||
|
||||
const firstTenantId = firstWorkorder.tenantId;
|
||||
|
||||
const allSameTenant = newVal.every(id => {
|
||||
const wo = this.$refs.table.$refs.table.rows.find(r => r.id === id);
|
||||
return wo && wo.tenantId === firstTenantId;
|
||||
});
|
||||
|
||||
if (!allSameTenant) {
|
||||
window.notify('error', 'Massen-Zuweisung nur für Aufträge des gleichen Mandanten möglich.');
|
||||
this.workordersToAssign.pop(); // remove last added
|
||||
return;
|
||||
}
|
||||
|
||||
await this.getCompaniesForWorkorder(firstWorkorder);
|
||||
this.companiesForMassAssign = this.companiesByTenant[firstTenantId] || [];
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -365,7 +414,8 @@ Vue.component('rml-documentation-viewer-admin', {
|
||||
<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>
|
||||
<ul class="list-unstyled">
|
||||
<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>
|
||||
@@ -409,6 +459,7 @@ Vue.component('rml-documentation-viewer-admin', {
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
loadingConfig: true,
|
||||
correctionLoading: false,
|
||||
docs: [],
|
||||
journals: [],
|
||||
@@ -416,7 +467,16 @@ Vue.component('rml-documentation-viewer-admin', {
|
||||
correctionText: '',
|
||||
newJournalMessage: '',
|
||||
addingJournalEntry: false,
|
||||
requiredDocTypes: [
|
||||
tenantDocTypes: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
requiredDocTypes() {
|
||||
if (this.tenantDocTypes) {
|
||||
return this.tenantDocTypes;
|
||||
}
|
||||
// Fallback for RML default
|
||||
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' },
|
||||
@@ -426,7 +486,7 @@ Vue.component('rml-documentation-viewer-admin', {
|
||||
{ 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: {
|
||||
@@ -445,6 +505,21 @@ Vue.component('rml-documentation-viewer-admin', {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
async loadTenantConfig() {
|
||||
this.loadingConfig = true;
|
||||
try {
|
||||
const response = await axios.get(`${window.TT_CONFIG.BASE_PATH}/RMLWorkorderCompany/getTenantConfig`, {
|
||||
params: { workorderId: this.workorderId }
|
||||
});
|
||||
if (response.data.success) {
|
||||
this.tenantDocTypes = response.data.documentationTypes;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Could not load tenant documentation config", 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.');
|
||||
@@ -498,7 +573,8 @@ Vue.component('rml-documentation-viewer-admin', {
|
||||
return window.moment.unix(timestamp).format('DD.MM.YYYY HH:mm');
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.fetchData();
|
||||
async mounted() {
|
||||
await this.loadTenantConfig();
|
||||
await this.fetchData();
|
||||
}
|
||||
});
|
||||
@@ -22,7 +22,7 @@ Vue.component('r-m-l-workorder-company', {
|
||||
</template>
|
||||
|
||||
<template v-slot:appointmentdate="{ row }">
|
||||
<div v-if="!row.appointmentDate && ['assigned', 'correction_requested'].includes(row.status)">
|
||||
<div v-if="!row.appointmentDate && ['assigned', 'correction_requested', 'problem_solved'].includes(row.status)">
|
||||
<tt-date-picker
|
||||
placeholder="Termin festlegen..."
|
||||
:date-range="false"
|
||||
@@ -193,7 +193,7 @@ Vue.component('documentation-manager', {
|
||||
props: ['workorderId'],
|
||||
template: `
|
||||
<div class="p-3 bg-light" style="width: 100%;">
|
||||
<div v-if="loadingWorkorder" class="text-center p-5"><i class="fas fa-spinner fa-spin fa-2x"></i></div>
|
||||
<div v-if="loadingWorkorder || loadingConfig" 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-4 mb-3 mb-lg-0">
|
||||
<div>
|
||||
@@ -327,6 +327,8 @@ Vue.component('documentation-manager', {
|
||||
data() {
|
||||
return {
|
||||
loadingWorkorder: true,
|
||||
loadingConfig: true,
|
||||
tenantDocTypes: null,
|
||||
workorder: null,
|
||||
uploading: false,
|
||||
completing: false,
|
||||
@@ -346,8 +348,16 @@ Vue.component('documentation-manager', {
|
||||
files: [],
|
||||
documentType: 'photo_hup_mounted',
|
||||
description: ''
|
||||
},
|
||||
requiredDocTypes: [
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
requiredDocTypes() {
|
||||
if (this.tenantDocTypes) {
|
||||
return this.tenantDocTypes;
|
||||
}
|
||||
// Fallback for RML default
|
||||
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' },
|
||||
@@ -357,10 +367,8 @@ Vue.component('documentation-manager', {
|
||||
{ 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)' },
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
];
|
||||
},
|
||||
allDocTypes() {
|
||||
return [
|
||||
...this.requiredDocTypes,
|
||||
@@ -403,6 +411,21 @@ Vue.component('documentation-manager', {
|
||||
if (!timestamp) return '–';
|
||||
return window.moment.unix(timestamp).format('DD.MM.YYYY HH:mm');
|
||||
},
|
||||
async loadTenantConfig() {
|
||||
this.loadingConfig = true;
|
||||
try {
|
||||
const response = await axios.get(`${window.TT_CONFIG.BASE_PATH}/RMLWorkorderCompany/getTenantConfig`, {
|
||||
params: { workorderId: this.workorderId }
|
||||
});
|
||||
if (response.data.success) {
|
||||
this.tenantDocTypes = response.data.documentationTypes;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Could not load tenant documentation config", e);
|
||||
} finally {
|
||||
this.loadingConfig = false;
|
||||
}
|
||||
},
|
||||
async loadWorkorder() {
|
||||
this.loadingWorkorder = true;
|
||||
try {
|
||||
@@ -545,7 +568,6 @@ Vue.component('documentation-manager', {
|
||||
}
|
||||
|
||||
let journalParts = [];
|
||||
// Sort types to have a consistent order in the journal message
|
||||
types.sort();
|
||||
|
||||
for (const type of types) {
|
||||
@@ -599,6 +621,7 @@ Vue.component('documentation-manager', {
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadWorkorder();
|
||||
await this.loadTenantConfig();
|
||||
await this.fetchDocs();
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user