381 lines
20 KiB
JavaScript
381 lines
20 KiB
JavaScript
// WorkorderTenantConfig.js
|
|
Vue.component('workorder-tenant-config', {
|
|
template: `
|
|
<tt-card>
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<ul class="nav nav-pills">
|
|
<li class="nav-item"><a class="nav-link" :class="{active: activeTab === 'configs'}" href="#"
|
|
@click.prevent="activeTab = 'configs'"><i class="fas fa-cogs mr-1"></i>
|
|
Mandanten-Konfigurationen</a></li>
|
|
<li class="nav-item"><a class="nav-link" :class="{active: activeTab === 'companies'}" href="#"
|
|
@click.prevent="activeTab = 'companies'"><i class="fas fa-building mr-1"></i>
|
|
Firmenverwaltung</a></li>
|
|
</ul>
|
|
<tt-button :text="activeTab === 'configs' ? 'Neue Konfiguration' : 'Neue Firma'" @click="openCreateModal"
|
|
icon="fas fa-plus" additional-class="btn-primary"/>
|
|
</div>
|
|
|
|
<div v-if="loading" class="text-center p-5"><i class="fas fa-spinner fa-spin fa-2x"></i></div>
|
|
|
|
<div v-show="!loading && activeTab === 'configs'">
|
|
<div v-for="config in configs" :key="config.id" class="card mb-3 config-card">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-file-alt mr-2"></i>
|
|
<tt-autocomplete v-if="editingId === config.id" :api-url="addressApiUrl"
|
|
v-model="editableItem.addressId" @input="editableItem.name = $event.text" no-form-group
|
|
sm/>
|
|
<strong v-else>{{ config.name }}</strong>
|
|
</h5>
|
|
<div>
|
|
<tt-button v-if="editingId !== config.id" @click="startEdit(config)" icon="fas fa-edit"
|
|
additional-class="btn-sm btn-outline-primary mr-2"/>
|
|
<tt-button v-if="editingId === config.id" @click="saveItem" icon="fas fa-save"
|
|
additional-class="btn-sm btn-success mr-2"/>
|
|
<tt-button v-if="editingId === config.id" @click="cancelEdit" icon="fas fa-times"
|
|
additional-class="btn-sm btn-secondary mr-2"/>
|
|
<tt-button @click="deleteItem(config, 'configs')" icon="fas fa-trash"
|
|
additional-class="btn-sm btn-outline-danger"/>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<h6>Dokumentationstypen</h6>
|
|
<tt-positions-manager :config="docTypesConfig" v-model="editableItem.documentationTypes"
|
|
v-if="editingId === config.id"/>
|
|
<ul v-else class="list-group list-group-flush type-list">
|
|
<li v-for="doc in JSON.parse(config.documentationTypes || '[]')" class="list-group-item">
|
|
<span>{{ doc.text }}</span><small class="code-font">{{ doc.value }}</small></li>
|
|
</ul>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h6>Interventionstypen</h6>
|
|
<tt-positions-manager :config="interventionTypesConfig" v-model="editableItem.interventionTypes"
|
|
v-if="editingId === config.id"/>
|
|
<ul v-else class="list-group list-group-flush type-list">
|
|
<li v-for="intervention in JSON.parse(config.interventionTypes || '[]')" class="list-group-item">
|
|
<span>{{ intervention.text }}</span><small class="code-font">{{ intervention.value }}</small></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<hr>
|
|
<div class="row mt-3">
|
|
<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-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>
|
|
<div v-if="editingId === config.id">
|
|
<tt-checkbox label="Workorder aktivieren"
|
|
v-model="editableItem.enableWorkorder" sm/>
|
|
<tt-checkbox label="WorkorderMPH aktivieren"
|
|
v-model="editableItem.enableWorkorderMph" sm/>
|
|
<hr>
|
|
<tt-checkbox label="Dokumentation für Tiefbau erforderlich"
|
|
v-model="editableItem.civilEngineeringDocsRequired" sm/>
|
|
<tt-checkbox label="Kabellänge erforderlich"
|
|
v-model="editableItem.requireCableLength" sm/>
|
|
<tt-checkbox label="Kabeltyp erforderlich"
|
|
v-model="editableItem.requireCableType" sm/>
|
|
<tt-checkbox label="Technische Daten anzeigen (Patchposition, AHA Blatt)"
|
|
v-model="editableItem.showTechnicalData" sm/>
|
|
<tt-checkbox label="Tiefbau sieht normale Dokumentationsschritte"
|
|
v-model="editableItem.tiefbauSeesNormalDocs" sm/>
|
|
</div>
|
|
<div v-else>
|
|
<p>Workorder: <strong>{{ config.enableWorkorder ? 'Aktiviert' : 'Deaktiviert' }}</strong></p>
|
|
<p>WorkorderMPH: <strong>{{ config.enableWorkorderMph ? 'Aktiviert' : 'Deaktiviert' }}</strong></p>
|
|
<hr>
|
|
<p>Tiefbau-Doku: <strong>{{ config.civilEngineeringDocsRequired ? 'Ja' : 'Nein' }}</strong></p>
|
|
<p>Kabellänge-Doku: <strong>{{ config.requireCableLength ? 'Ja' : 'Nein' }}</strong></p>
|
|
<p>Kabeltyp-Doku: <strong>{{ config.requireCableType ? 'Ja' : 'Nein' }}</strong></p>
|
|
<p>Technische Daten: <strong>{{ config.showTechnicalData ? 'Ja' : 'Nein' }}</strong></p>
|
|
<p>Tiefbau sieht Doku: <strong>{{ config.tiefbauSeesNormalDocs ? 'Ja' : 'Nein' }}</strong></p>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h6>Zugeordnete Firmen</h6>
|
|
<ul v-if="companiesByTenantMap[config.addressId] && companiesByTenantMap[config.addressId].length"
|
|
class="list-group">
|
|
<li v-for="company in companiesByTenantMap[config.addressId]" class="list-group-item py-1"><i
|
|
class="fas fa-building mr-2 text-muted"></i>{{ company.name }}
|
|
</li>
|
|
</ul>
|
|
<div v-else class="text-muted p-3">Keine Firmen zugeordnet.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-show="!loading && activeTab === 'companies'">
|
|
<div v-for="company in companies" :key="company.id" class="card mb-3 config-card">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0"><i class="fas fa-building mr-2"></i><strong>{{ company.name }}</strong></h5>
|
|
<div>
|
|
<tt-button v-if="editingId !== company.id" @click="startEdit(company)" icon="fas fa-edit"
|
|
additional-class="btn-sm btn-outline-primary mr-2"/>
|
|
<tt-button v-if="editingId === company.id" @click="saveItem" icon="fas fa-save"
|
|
additional-class="btn-sm btn-success mr-2"/>
|
|
<tt-button v-if="editingId === company.id" @click="cancelEdit" icon="fas fa-times"
|
|
additional-class="btn-sm btn-secondary mr-2"/>
|
|
<tt-button @click="deleteItem(company, 'companies')" icon="fas fa-trash"
|
|
additional-class="btn-sm btn-outline-danger"/>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<h6>Sichtbar für Mandanten</h6>
|
|
<div v-if="editingId === company.id">
|
|
<ul class="list-group tenant-edit-list mb-2">
|
|
<li v-for="(tenantId, index) in editableItem.visibleForAddressId" :key="tenantId"
|
|
class="list-group-item d-flex justify-content-between align-items-center">
|
|
<tt-resolver :value="tenantId" reference="/WorkorderTenantConfig/addressAutocomplete"
|
|
:autocomplete="true"/>
|
|
<tt-button icon="fas fa-trash" @click="removeTenant(index)"
|
|
additional-class="btn-sm btn-link text-danger"/>
|
|
</li>
|
|
</ul>
|
|
<tt-autocomplete :api-url="addressApiUrl" v-model="tenantToAdd" @input="addTenant"
|
|
placeholder="Mandant hinzufügen..." sm no-form-group/>
|
|
</div>
|
|
<ul v-else class="list-group list-group-flush">
|
|
<li v-for="tenantId in JSON.parse(company.visibleForAddressId || '[]')"
|
|
class="list-group-item py-1">
|
|
<tt-resolver :value="tenantId" reference="/WorkorderTenantConfig/addressAutocomplete"
|
|
:autocomplete="true"/>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h6>Zugehörige Mitarbeiter <small>(mit RMLCompany-Recht)</small></h6>
|
|
<ul v-if="company.workers && company.workers.length" class="list-group list-group-flush">
|
|
<li v-for="worker in company.workers" class="list-group-item py-1">{{ worker.name }} (
|
|
{{ worker.email }})
|
|
</li>
|
|
</ul>
|
|
<div v-else class="text-muted p-3">Keine Mitarbeiter für diese Firma gefunden.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<tt-modal v-if="showModal" :show.sync="showModal" :title="modalTitle" @submit="saveNewItem" :delete="false">
|
|
<div v-if="activeTab === 'configs'">
|
|
<tt-autocomplete label="Mandant" :api-url="addressApiUrl" v-model="newItem.addressId"
|
|
@input="newItem.name = $event.text" sm row required/>
|
|
</div>
|
|
<div v-if="activeTab === 'companies'">
|
|
<tt-autocomplete label="Firma" :api-url="addressApiUrl" v-model="newItem.addressId"
|
|
@input="newItem.name = $event.text" sm row required/>
|
|
<div class="form-group row">
|
|
<label class="col-form-label col-sm-4 col-form-label-sm">Sichtbar für Mandanten</label>
|
|
<div class="col-sm-8">
|
|
<div class="tenant-tags-container mb-2">
|
|
<span v-for="(tenantId, index) in newItem.visibleForAddressId" :key="tenantId"
|
|
class="badge badge-primary tenant-tag">
|
|
<tt-resolver :value="tenantId" reference="/WorkorderTenantConfig/addressAutocomplete"
|
|
:autocomplete="true"/>
|
|
<i class="fas fa-times-circle" @click="removeTenant(index, true)"></i>
|
|
</span>
|
|
</div>
|
|
<tt-autocomplete :api-url="addressApiUrl" v-model="tenantToAdd" @input="addTenant($event, true)"
|
|
placeholder="Mandant hinzufügen..." sm no-form-group/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</tt-modal>
|
|
</tt-card>
|
|
`,
|
|
data() {
|
|
return {
|
|
loading: true, activeTab: 'configs', configs: [], companies: [], editingId: null, editableItem: {},
|
|
editableJsonFilter: '', editableJsonActiveFilter: '', showModal: false, newItem: {}, tenantToAdd: null,
|
|
addressApiUrl: `${window.TT_CONFIG.BASE_PATH}/WorkorderTenantConfig/addressAutocomplete`,
|
|
docTypesConfig: {
|
|
fields: {
|
|
text: {type: 'input', label: 'Anzeigename'},
|
|
value: {type: 'input', label: 'Schlüssel'}
|
|
}
|
|
},
|
|
interventionTypesConfig: {
|
|
fields: {
|
|
text: {type: 'input', label: 'Anzeigename'},
|
|
value: {type: 'input', label: 'Schlüssel'}
|
|
}
|
|
},
|
|
};
|
|
},
|
|
computed: {
|
|
modalTitle() {
|
|
return this.activeTab === 'configs' ? 'Neue Konfiguration erstellen' : 'Neue Firma anlegen';
|
|
},
|
|
companiesByTenantMap() {
|
|
const map = {};
|
|
this.configs.forEach(config => {
|
|
map[config.addressId] = [];
|
|
});
|
|
this.companies.forEach(company => {
|
|
try {
|
|
const visibleFor = JSON.parse(company.visibleForAddressId || '[]');
|
|
if (Array.isArray(visibleFor)) {
|
|
visibleFor.forEach(tenantId => {
|
|
if (map[tenantId]) map[tenantId].push(company);
|
|
});
|
|
}
|
|
} catch (e) {
|
|
}
|
|
});
|
|
return map;
|
|
}
|
|
},
|
|
methods: {
|
|
async fetchData() {
|
|
this.loading = true;
|
|
try {
|
|
const [configsRes, companiesRes] = await Promise.all([
|
|
axios.get(`${window.TT_CONFIG.BASE_PATH}/WorkorderTenantConfig/getTenantConfigs`),
|
|
axios.get(`${window.TT_CONFIG.BASE_PATH}/WorkorderTenantConfig/getCompanies`),
|
|
]);
|
|
this.configs = configsRes.data;
|
|
this.companies = companiesRes.data;
|
|
} catch (e) {
|
|
window.notify('error', 'Daten konnten nicht geladen werden.');
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
startEdit(item) {
|
|
this.editingId = item.id;
|
|
this.editableItem = JSON.parse(JSON.stringify(item));
|
|
if (this.activeTab === 'configs') {
|
|
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 || '[]');
|
|
} catch (e) {
|
|
this.editableItem.visibleForAddressId = [];
|
|
}
|
|
}
|
|
},
|
|
cancelEdit() {
|
|
this.editingId = null;
|
|
this.editableItem = {};
|
|
this.editableJsonFilter = '';
|
|
this.editableJsonActiveFilter = '';
|
|
},
|
|
async saveItem() {
|
|
const endpoint = this.activeTab === 'configs' ? 'saveTenantConfig' : 'saveCompany';
|
|
let payload = {...this.editableItem};
|
|
|
|
if (this.activeTab === 'configs') {
|
|
try {
|
|
JSON.parse(this.editableJsonFilter);
|
|
payload.workorderCreationFilters = this.editableJsonFilter;
|
|
} 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 {
|
|
const {data} = await axios.post(`${window.TT_CONFIG.BASE_PATH}/WorkorderTenantConfig/${endpoint}`, payload);
|
|
if (data.success) {
|
|
window.notify('success', data.message);
|
|
this.cancelEdit();
|
|
await this.fetchData();
|
|
} else {
|
|
window.notify('error', data.message || 'Speichern fehlgeschlagen.');
|
|
}
|
|
} catch (e) {
|
|
window.notify('error', 'Ein Netzwerkfehler ist aufgetreten.');
|
|
}
|
|
},
|
|
async deleteItem(item, type) {
|
|
if (!confirm(`Soll der Eintrag "${item.name}" wirklich gelöscht werden?`)) return;
|
|
const endpoint = type === 'configs' ? 'deleteTenantConfig' : 'deleteCompany';
|
|
try {
|
|
const {data} = await axios.post(`${window.TT_CONFIG.BASE_PATH}/WorkorderTenantConfig/${endpoint}`, {id: item.id});
|
|
if (data.success) {
|
|
window.notify('success', data.message);
|
|
await this.fetchData();
|
|
} else {
|
|
window.notify('error', data.message || 'Löschen fehlgeschlagen.');
|
|
}
|
|
} catch (e) {
|
|
window.notify('error', 'Ein Netzwerkfehler ist aufgetreten.');
|
|
}
|
|
},
|
|
openCreateModal() {
|
|
this.newItem = this.activeTab === 'configs'
|
|
? {
|
|
documentationTypes: [],
|
|
interventionTypes: [],
|
|
workorderCreationFilters: '{}',
|
|
workorderActiveFilters: '{}',
|
|
civilEngineeringDocsRequired: 0,
|
|
requireCableLength: 0,
|
|
requireCableType: 0,
|
|
showTechnicalData: 0,
|
|
tiefbauSeesNormalDocs: 0,
|
|
enableWorkorder: 1,
|
|
enableWorkorderMph: 1
|
|
}
|
|
: {visibleForAddressId: []};
|
|
this.showModal = true;
|
|
},
|
|
async saveNewItem() {
|
|
const endpoint = this.activeTab === 'configs' ? 'saveTenantConfig' : 'saveCompany';
|
|
try {
|
|
const {data} = await axios.post(`${window.TT_CONFIG.BASE_PATH}/WorkorderTenantConfig/${endpoint}`, this.newItem);
|
|
if (data.success) {
|
|
window.notify('success', data.message);
|
|
this.showModal = false;
|
|
await this.fetchData();
|
|
} else {
|
|
window.notify('error', data.message || 'Speichern fehlgeschlagen.');
|
|
}
|
|
} catch (e) {
|
|
window.notify('error', 'Ein Netzwerkfehler ist aufgetreten.');
|
|
}
|
|
},
|
|
addTenant(tenant, isNewItem = false) {
|
|
const tenantId = typeof tenant === 'object' && tenant !== null ? tenant.value : tenant;
|
|
const targetArray = isNewItem ? this.newItem.visibleForAddressId : this.editableItem.visibleForAddressId;
|
|
if (tenantId && !targetArray.includes(tenantId)) targetArray.push(tenantId);
|
|
this.$nextTick(() => {
|
|
this.tenantToAdd = null;
|
|
});
|
|
},
|
|
removeTenant(index, isNewItem = false) {
|
|
const targetArray = isNewItem ? this.newItem.visibleForAddressId : this.editableItem.visibleForAddressId;
|
|
targetArray.splice(index, 1);
|
|
},
|
|
},
|
|
async mounted() {
|
|
await this.fetchData();
|
|
}
|
|
}); |