318 lines
12 KiB
JavaScript
318 lines
12 KiB
JavaScript
// =================================================================================
|
|
// Main Asset Management Component
|
|
// =================================================================================
|
|
Vue.component('asset-management', {
|
|
template: `
|
|
<tt-card>
|
|
<asset-management-modal
|
|
v-if="modalId"
|
|
:id="modalId"
|
|
@close="modalId = null; $refs.table.refreshTable()"/>
|
|
<asset-management-journal-modal
|
|
v-if="journalModalAssetId"
|
|
:asset-id="journalModalAssetId"
|
|
@close="journalModalAssetId = null"/>
|
|
|
|
<tt-table-crud
|
|
ref="table"
|
|
@edit="modalId = $event.id"
|
|
:crud-config="crudConfig">
|
|
|
|
<template v-slot:currentuser="{ row }">
|
|
<asset-borrow-return-widget
|
|
:row-data="row"
|
|
@update="$refs.table.refreshTable()"/>
|
|
</template>
|
|
|
|
<template v-slot:journal="{ row }">
|
|
<tt-button
|
|
sm
|
|
icon="fas fa-history"
|
|
@click="journalModalAssetId = row.id"
|
|
additional-class="btn-outline-info"/>
|
|
</template>
|
|
|
|
<template v-slot:serviceduedate="{ row }">
|
|
<span v-if="row.serviceDueDate">
|
|
{{ window.moment.unix(row.serviceDueDate).format('DD.MM.YYYY') }}
|
|
</span>
|
|
</template>
|
|
|
|
<template v-slot:borrowdate="{ row }">
|
|
<span v-if="row.borrowDate">
|
|
{{ window.moment.unix(row.borrowDate).format('DD.MM.YYYY') }}
|
|
</span>
|
|
</template>
|
|
|
|
</tt-table-crud>
|
|
</tt-card>
|
|
`,
|
|
data() {
|
|
return {
|
|
window: window,
|
|
modalId: null,
|
|
journalModalAssetId: null,
|
|
crudConfig: window.TT_CONFIG.CRUD_CONFIG, // Assumes config is passed globally
|
|
}
|
|
},
|
|
});
|
|
|
|
// =================================================================================
|
|
// Asset Borrow/Return Widget Component (for the table column)
|
|
// =================================================================================
|
|
Vue.component('asset-borrow-return-widget', {
|
|
props: {
|
|
rowData: { type: Object, required: true }
|
|
},
|
|
template: `
|
|
<div>
|
|
<div v-if="rowData.currentUserId" class="d-flex align-items-center">
|
|
<span>{{ rowData.currentUser }}</span>
|
|
<tt-button
|
|
sm
|
|
text="Zurück"
|
|
@click="showReturnModal = true"
|
|
additional-class="btn-success ml-2"/>
|
|
</div>
|
|
|
|
<div v-else>
|
|
<tt-autocomplete
|
|
:label="null"
|
|
:api-url="userAutoCompleteUrl"
|
|
placeholder="Mitarbeiter..."
|
|
sm
|
|
no-form-group
|
|
v-model="selectedUserId"
|
|
@input="onUserSelect"
|
|
/>
|
|
</div>
|
|
|
|
<tt-modal v-if="showBorrowModal" :show="true" title="Gerät ausleihen" @update:show="showBorrowModal = false" @submit="borrowAsset">
|
|
<p><strong>Gerät:</strong> {{ rowData.name }}</p>
|
|
<p><strong>Mitarbeiter:</strong> {{ selectedUserName }}</p>
|
|
<tt-input label="Baustelle / Projekt" v-model="borrowSite" sm row required/>
|
|
<tt-textarea label="Grund" v-model="borrowReason" sm row required/>
|
|
</tt-modal>
|
|
|
|
<tt-modal v-if="showReturnModal" :show="true" title="Gerät zurückgeben" @update:show="showReturnModal = false" @submit="returnAsset">
|
|
<p><strong>Gerät:</strong> {{ rowData.name }}</p>
|
|
<p>Soll dieses Gerät wirklich als zurückgegeben markiert werden?</p>
|
|
<tt-textarea label="Bemerkung (optional)" v-model="returnReason" sm row/>
|
|
</tt-modal>
|
|
</div>
|
|
`,
|
|
data() {
|
|
return {
|
|
window: window,
|
|
userAutoCompleteUrl: window.TT_CONFIG.BASE_PATH + '/WarehouseShippingNote/userAutoComplete', // Re-using existing one
|
|
selectedUserId: null,
|
|
selectedUserName: '',
|
|
showBorrowModal: false,
|
|
showReturnModal: false,
|
|
borrowSite: '',
|
|
borrowReason: '',
|
|
returnReason: ''
|
|
}
|
|
},
|
|
methods: {
|
|
async onUserSelect(userId) {
|
|
if (!userId) return;
|
|
this.selectedUserId = userId;
|
|
|
|
// Fetch user name to display in modal
|
|
const response = await axios.get(`${this.userAutoCompleteUrl}&searchedID=${userId}`);
|
|
this.selectedUserName = response.data[0]?.text || 'Unbekannt';
|
|
|
|
this.showBorrowModal = true;
|
|
},
|
|
async borrowAsset() {
|
|
if (!this.borrowSite || !this.borrowReason) {
|
|
return window.notify('error', 'Bitte Baustelle und Grund angeben.');
|
|
}
|
|
try {
|
|
const response = await axios.post(`${window.TT_CONFIG.BASE_PATH}/AssetManagement/borrow`, {
|
|
assetId: this.rowData.id,
|
|
userId: this.selectedUserId,
|
|
site: this.borrowSite,
|
|
reason: this.borrowReason
|
|
});
|
|
if (response.data.success) {
|
|
window.notify('success', response.data.message);
|
|
this.showBorrowModal = false;
|
|
this.$emit('update');
|
|
} else {
|
|
window.notify('error', response.data.message || 'Fehler beim Ausleihen.');
|
|
}
|
|
} catch (error) {
|
|
window.notify('error', 'Ein Fehler ist aufgetreten.');
|
|
}
|
|
},
|
|
async returnAsset() {
|
|
try {
|
|
const response = await axios.post(`${window.TT_CONFIG.BASE_PATH}/AssetManagement/return`, {
|
|
journalId: this.rowData.journalId,
|
|
reason: this.returnReason
|
|
});
|
|
if (response.data.success) {
|
|
window.notify('success', response.data.message);
|
|
this.showReturnModal = false;
|
|
this.$emit('update');
|
|
} else {
|
|
window.notify('error', response.data.message || 'Fehler beim Zurückgeben.');
|
|
}
|
|
} catch (error) {
|
|
window.notify('error', 'Ein Fehler ist aufgetreten.');
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
// =================================================================================
|
|
// Asset Create/Edit Modal
|
|
// =================================================================================
|
|
Vue.component('asset-management-modal', {
|
|
props: ['id'],
|
|
template: `
|
|
<tt-modal
|
|
:show="true"
|
|
:title="isCreateMode ? 'Gerät anlegen' : 'Gerät bearbeiten'"
|
|
@update:show="$emit('close')"
|
|
@submit="submit"
|
|
:delete="!isCreateMode"
|
|
@delete="deleteAsset"
|
|
>
|
|
<tt-input label="Gerätename" v-model="asset.name" sm row required/>
|
|
<tt-input label="Kennzeichen / Nr." v-model="asset.assetNumber" sm row required>
|
|
<template v-slot:prepend>
|
|
<button class="btn btn-sm btn-link" @click="suggestAssetNumber" title="Nächste Nummer vorschlagen">
|
|
<i class="fas fa-magic"></i>
|
|
</button>
|
|
</template>
|
|
</tt-input>
|
|
<tt-input label="Lagerort" v-model="asset.location" sm row required/>
|
|
<tt-date-picker label="Nächstes Service" v-model="asset.serviceDueDate" sm row :date-range="false"/>
|
|
</tt-modal>
|
|
`,
|
|
data() {
|
|
return {
|
|
asset: {
|
|
name: '',
|
|
assetNumber: '',
|
|
location: 'Hauptlager',
|
|
serviceDueDate: null
|
|
},
|
|
}
|
|
},
|
|
computed: {
|
|
isCreateMode() {
|
|
return this.id === 'create';
|
|
}
|
|
},
|
|
async mounted() {
|
|
if (!this.isCreateMode) {
|
|
const response = await axios.get(`${window.TT_CONFIG.BASE_PATH}/AssetManagement/getById`, { params: { id: this.id } });
|
|
this.asset = response.data;
|
|
} else {
|
|
await this.suggestAssetNumber();
|
|
}
|
|
},
|
|
methods: {
|
|
async submit() {
|
|
const url = this.isCreateMode
|
|
? `${window.TT_CONFIG.BASE_PATH}/AssetManagement/create`
|
|
: `${window.TT_CONFIG.BASE_PATH}/AssetManagement/update`;
|
|
|
|
try {
|
|
const response = await axios.post(url, this.asset);
|
|
if (response.data.success) {
|
|
window.notify('success', response.data.message || 'Erfolgreich gespeichert.');
|
|
this.$emit('close');
|
|
} else {
|
|
window.notify('error', response.data.message || 'Ein Fehler ist aufgetreten.');
|
|
}
|
|
} catch (error) {
|
|
window.notify('error', 'Ein Netzwerkfehler ist aufgetreten.');
|
|
}
|
|
},
|
|
async deleteAsset() {
|
|
if (!confirm('Soll dieses Gerät wirklich gelöscht werden?')) return;
|
|
// Also check for journal entries before deleting
|
|
try {
|
|
const response = await axios.post(`${window.TT_CONFIG.BASE_PATH}/AssetManagement/delete`, { id: this.id });
|
|
if (response.data.success) {
|
|
window.notify('success', 'Gerät gelöscht.');
|
|
this.$emit('close');
|
|
} else {
|
|
window.notify('error', response.data.message || 'Fehler beim Löschen.');
|
|
}
|
|
} catch (error) {
|
|
window.notify('error', 'Ein Netzwerkfehler ist aufgetreten.');
|
|
}
|
|
},
|
|
async suggestAssetNumber() {
|
|
const response = await axios.get(`${window.TT_CONFIG.BASE_PATH}/AssetManagement/suggestAssetNumber`);
|
|
if (response.data.success) {
|
|
this.$set(this.asset, 'assetNumber', response.data.assetNumber);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// =================================================================================
|
|
// Asset Journal/History Modal
|
|
// =================================================================================
|
|
Vue.component('asset-management-journal-modal', {
|
|
props: {
|
|
assetId: { type: Number, required: true }
|
|
},
|
|
template: `
|
|
<tt-modal :show="true" title="Gerätehistorie" :save="false" :delete="false" @update:show="$emit('close');">
|
|
<div v-if="loading" class="text-center"><i class="fas fa-spinner fa-spin"></i> Lade...</div>
|
|
<div v-else>
|
|
<div v-if="!journalEntries.length" class="text-center text-muted">Keine Einträge vorhanden.</div>
|
|
<ul v-else class="list-group">
|
|
<li v-for="entry in journalEntries" class="list-group-item">
|
|
<div class="d-flex w-100 justify-content-between">
|
|
<h5 class="mb-1">{{ entry.userName }} @ {{ entry.site }}</h5>
|
|
<small>{{ formatDate(entry.borrowDate) }}</small>
|
|
</div>
|
|
<p class="mb-1"><strong>Grund:</strong> {{ entry.borrowReason }}</p>
|
|
<div v-if="entry.returnDate">
|
|
<small class="text-success">
|
|
<strong>Zurück am:</strong> {{ formatDate(entry.returnDate) }}
|
|
<br>
|
|
<strong>Bemerkung:</strong> {{ entry.returnReason }}
|
|
</small>
|
|
</div>
|
|
<div v-else>
|
|
<small class="text-warning"><strong>Aktuell ausgeliehen</strong></small>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</tt-modal>
|
|
`,
|
|
data() {
|
|
return {
|
|
loading: false,
|
|
journalEntries: []
|
|
}
|
|
},
|
|
async mounted() {
|
|
this.loading = true;
|
|
try {
|
|
const response = await axios.get(`${window.TT_CONFIG.BASE_PATH}/AssetManagement/getJournal`, { params: { assetId: this.assetId } });
|
|
this.journalEntries = response.data;
|
|
} catch (error) {
|
|
window.notify('error', 'Historie konnte nicht geladen werden.');
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
methods: {
|
|
formatDate(timestamp) {
|
|
return window.moment.unix(timestamp).format('DD.MM.YYYY HH:mm');
|
|
}
|
|
}
|
|
}); |