added new asset management poc
This commit is contained in:
149
application/AssetManagement/AssetManagementController.php
Normal file
149
application/AssetManagement/AssetManagementController.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
class AssetManagementController extends TTCrud
|
||||
{
|
||||
protected string $headerTitle = 'Anlagenverwaltung';
|
||||
protected string $singleText = 'Anlage';
|
||||
|
||||
protected array $columns = [
|
||||
['key' => 'id', 'text' => 'ID', 'modal' => false, 'table' => false],
|
||||
['key' => 'name', 'text' => 'Gerät', 'required' => true, 'modal' => ['type' => 'text']],
|
||||
['key' => 'assetNumber', 'text' => 'Kennzeichen / Nr.', 'required' => true, 'modal' => ['type' => 'text']],
|
||||
['key' => 'currentUser', 'text' => 'Akt. Mitarbeiter', 'modal' => false, 'table' => ['sortable' => false]],
|
||||
['key' => 'currentSite', 'text' => 'Akt. Baustelle', 'modal' => false, 'table' => ['sortable' => false]],
|
||||
['key' => 'borrowDate', 'text' => 'Ausgeliehen seit', 'modal' => false, 'table' => ['sortable' => false]],
|
||||
['key' => 'location', 'text' => 'Lagerort', 'required' => true, 'modal' => ['type' => 'text']],
|
||||
['key' => 'serviceDueDate', 'text' => 'Service fällig', 'required' => false, 'modal' => ['type' => 'datepicker']],
|
||||
['key' => 'journal', 'text' => 'Historie', 'modal' => false, 'table' => ['sortable' => false, 'filter' => false]],
|
||||
['key' => 'actions', 'text' => 'Aktionen', 'modal' => false, 'table' => ['filter' => false, 'sortable' => false]],
|
||||
];
|
||||
|
||||
protected array $permissionCheck = ['WarehouseAdmin']; // Or a new permission
|
||||
|
||||
protected function getAction()
|
||||
{
|
||||
$json = json_decode(file_get_contents('php://input'), true);
|
||||
$assets = AssetManagementModel::getAll([], $this->request->order);
|
||||
$assetIds = array_map(fn($asset) => $asset->id, $assets);
|
||||
|
||||
if (empty($assetIds)) {
|
||||
self::returnJson(['rows' => [], 'pagination' => ['total_rows' => 0, 'total_pages' => 1, 'page' => 1, 'per_page' => 10, 'filtered_available' => 0]]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the latest open journal entry for each asset
|
||||
$journalEntries = AssetManagementJournalModel::getLatestOpenEntries($assetIds);
|
||||
$journalMap = [];
|
||||
foreach ($journalEntries as $entry) {
|
||||
// Only map it if it's not returned
|
||||
if ($entry->returnDate === null) {
|
||||
$journalMap[$entry->assetId] = $entry;
|
||||
}
|
||||
}
|
||||
|
||||
$users = UserModel::search(['employee' => true]);
|
||||
$userMap = array_reduce($users, function ($carry, $user) {
|
||||
$carry[$user->id] = $user->name;
|
||||
return $carry;
|
||||
}, []);
|
||||
|
||||
$rows = [];
|
||||
foreach ($assets as $asset) {
|
||||
$row = (array)$asset;
|
||||
$latestJournal = $journalMap[$asset->id] ?? null;
|
||||
|
||||
$row['journalId'] = $latestJournal->id ?? null;
|
||||
$row['currentUser'] = $latestJournal ? ($userMap[$latestJournal->userId] ?? 'Unbekannt') : null;
|
||||
$row['currentUserId'] = $latestJournal->userId ?? null;
|
||||
$row['currentSite'] = $latestJournal->site ?? null;
|
||||
$row['borrowDate'] = $latestJournal->borrowDate ?? null;
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
// Simple pagination/filtering after getting all data
|
||||
// For larger datasets, this should be done in the SQL query
|
||||
$totalRows = count($rows);
|
||||
$pagination = $json['pagination'] ?? ['page' => 1, 'per_page' => 10];
|
||||
$paginatedRows = array_slice($rows, ($pagination['page'] - 1) * $pagination['per_page'], $pagination['per_page']);
|
||||
|
||||
self::returnJson([
|
||||
'rows' => $paginatedRows,
|
||||
'pagination' => [
|
||||
'page' => $pagination['page'],
|
||||
'per_page' => $pagination['per_page'],
|
||||
'total_rows' => $totalRows,
|
||||
'total_pages' => ceil($totalRows / $pagination['per_page'])
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
protected function suggestAssetNumberAction()
|
||||
{
|
||||
$lastAsset = AssetManagementModel::getOne([], ['order' => 'DESC', 'key' => 'id']);
|
||||
if (!$lastAsset || !preg_match('/XI(\d+)/', $lastAsset->assetNumber, $matches)) {
|
||||
$nextNumber = 1;
|
||||
} else {
|
||||
$nextNumber = intval($matches[1]) + 1;
|
||||
}
|
||||
$newAssetNumber = 'XI' . str_pad($nextNumber, 3, '0', STR_PAD_LEFT);
|
||||
self::returnJson(['success' => true, 'assetNumber' => $newAssetNumber]);
|
||||
}
|
||||
|
||||
protected function borrowAction()
|
||||
{
|
||||
$post = json_decode(file_get_contents('php://input'), true);
|
||||
if (empty($post['assetId']) || empty($post['userId']) || empty($post['site']) || empty($post['reason'])) {
|
||||
self::sendError("Alle Felder sind erforderlich.");
|
||||
}
|
||||
|
||||
AssetManagementJournalModel::create([
|
||||
'assetId' => $post['assetId'],
|
||||
'userId' => $post['userId'],
|
||||
'site' => $post['site'],
|
||||
'borrowReason' => $post['reason'],
|
||||
'borrowDate' => time(),
|
||||
'createBy' => $this->user->id,
|
||||
'create' => time(),
|
||||
]);
|
||||
|
||||
self::returnJson(['success' => true, 'message' => 'Gerät erfolgreich ausgeliehen.']);
|
||||
}
|
||||
|
||||
protected function returnAction()
|
||||
{
|
||||
$post = json_decode(file_get_contents('php://input'), true);
|
||||
if (empty($post['journalId'])) {
|
||||
self::sendError("Journal-Eintrag nicht gefunden.");
|
||||
}
|
||||
|
||||
$journalEntry = AssetManagementJournalModel::get($post['journalId']);
|
||||
if (!$journalEntry) {
|
||||
self::sendError("Journal-Eintrag nicht gefunden.");
|
||||
}
|
||||
|
||||
$journalEntry->returnDate = time();
|
||||
$journalEntry->returnReason = $post['reason'] ?? 'Zurückgegeben';
|
||||
AssetManagementJournalModel::update((array)$journalEntry);
|
||||
|
||||
self::returnJson(['success' => true, 'message' => 'Gerät erfolgreich zurückgegeben.']);
|
||||
}
|
||||
|
||||
protected function getJournalAction()
|
||||
{
|
||||
if (empty($this->request->assetId)) self::sendError("Asset ID fehlt.");
|
||||
$entries = AssetManagementJournalModel::getAll(['assetId' => $this->request->assetId], null, 0, ['key' => 'borrowDate', 'order' => 'DESC']);
|
||||
|
||||
// Enhance with user names
|
||||
$users = UserModel::search(['employee' => true]);
|
||||
$userMap = array_reduce($users, function ($carry, $user) {
|
||||
$carry[$user->id] = $user->name;
|
||||
return $carry;
|
||||
}, []);
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
$entry->userName = $userMap[$entry->userId] ?? 'Unbekannt';
|
||||
}
|
||||
|
||||
self::returnJson($entries);
|
||||
}
|
||||
}
|
||||
11
application/AssetManagement/AssetManagementModel.php
Normal file
11
application/AssetManagement/AssetManagementModel.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
class AssetManagementModel extends TTCrudBaseModel {
|
||||
public int $id;
|
||||
public string $name;
|
||||
public string $assetNumber;
|
||||
public string $location;
|
||||
public ?string $serviceDueDate;
|
||||
public int $create;
|
||||
public int $createBy;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
class AssetManagementJournalModel extends TTCrudBaseModel {
|
||||
public int $id;
|
||||
public int $assetId;
|
||||
public int $userId;
|
||||
public string $site;
|
||||
public int $borrowDate;
|
||||
public ?int $returnDate;
|
||||
public string $borrowReason;
|
||||
public ?string $returnReason;
|
||||
public int $createBy;
|
||||
public int $create;
|
||||
|
||||
// Get the latest open journal entry for each asset
|
||||
public static function getLatestOpenEntries($assetIds): array {
|
||||
$db = self::getDB();
|
||||
$table = self::getFullyQualifiedTable();
|
||||
$sql = "SELECT j1.*
|
||||
FROM AssetManagementJournal j1
|
||||
LEFT JOIN AssetManagementJournal j2
|
||||
ON j1.assetId = j2.assetId AND j1.borrowDate < j2.borrowDate
|
||||
WHERE j2.id IS NULL AND j1.assetId IN (" . implode(',', $assetIds) . ")";
|
||||
$result = $db->query($sql);
|
||||
|
||||
$entries = [];
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$entries[] = new self($row);
|
||||
}
|
||||
return $entries;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/** @noinspection ALL */
|
||||
declare(strict_types=1);
|
||||
|
||||
use Phinx\Migration\AbstractMigration;
|
||||
|
||||
final class CreateAssetManagementTables extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* Create the asset management tables.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// Environment check to ensure this migration only runs on the specified environment.
|
||||
if ($this->getEnvironment() == "thetool") {
|
||||
|
||||
// Create the main asset table
|
||||
$assetManagement = $this->table('AssetManagement', ['id' => false, 'primary_key' => ['id']]);
|
||||
$assetManagement->addColumn('id', 'integer', ['identity' => true, 'signed' => false])
|
||||
->addColumn('name', 'string', ['limit' => 255, 'null' => false, 'comment' => 'Name of the asset or device'])
|
||||
->addColumn('assetNumber', 'string', ['limit' => 255, 'null' => false, 'comment' => 'Unique identifier for the asset (e.g., XI001)'])
|
||||
->addColumn('location', 'string', ['limit' => 255, 'null' => false, 'comment' => 'The primary storage location of the asset'])
|
||||
->addColumn('serviceDueDate', 'integer', ['null' => true, 'comment' => 'Date when the next service is due'])
|
||||
->addColumn('create', 'integer', ['null' => false, 'comment' => 'Unix timestamp of creation'])
|
||||
->addColumn('createBy', 'integer', ['null' => false, 'comment' => 'User ID of the creator'])
|
||||
->addIndex(['assetNumber'], ['unique' => true, 'name' => 'assetNumber'])
|
||||
->create();
|
||||
|
||||
// Create the journal table for asset borrowing and returning
|
||||
$assetManagementJournal = $this->table('AssetManagementJournal', ['id' => false, 'primary_key' => ['id']]);
|
||||
$assetManagementJournal->addColumn('id', 'integer', ['identity' => true, 'signed' => false])
|
||||
->addColumn('assetId', 'integer', ['null' => false, 'comment' => 'Foreign key to the AssetManagement table'])
|
||||
->addColumn('userId', 'integer', ['null' => false, 'comment' => 'User ID of the employee who borrowed the asset'])
|
||||
->addColumn('site', 'string', ['limit' => 255, 'null' => false, 'comment' => 'The construction site or project where the asset is being used'])
|
||||
->addColumn('borrowDate', 'integer', ['null' => false, 'comment' => 'Unix timestamp when the asset was borrowed'])
|
||||
->addColumn('returnDate', 'integer', ['null' => true, 'comment' => 'Unix timestamp when the asset was returned'])
|
||||
->addColumn('borrowReason', 'text', ['null' => false, 'comment' => 'Reason for borrowing the asset'])
|
||||
->addColumn('returnReason', 'text', ['null' => true, 'comment' => 'Notes or reason for returning the asset'])
|
||||
->addColumn('createBy', 'integer', ['null' => false, 'comment' => 'User ID of the person who created this journal entry'])
|
||||
->addColumn('create', 'integer', ['null' => false, 'comment' => 'Unix timestamp of journal entry creation'])
|
||||
->addIndex(['assetId'], ['name' => 'assetId'])
|
||||
->addIndex(['userId'], ['name' => 'userId'])
|
||||
->create();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback the migration by dropping the tables.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// Environment check to ensure this migration only runs on the specified environment.
|
||||
if ($this->getEnvironment() == "thetool") {
|
||||
// Drop tables in reverse order of creation to avoid foreign key issues.
|
||||
$this->table('AssetManagementJournal')->drop()->save();
|
||||
$this->table('AssetManagement')->drop()->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,14 +10,14 @@ class TTCrudBaseModel {
|
||||
}
|
||||
}
|
||||
|
||||
private static function getFullyQualifiedTable(): string {
|
||||
protected static function getFullyQualifiedTable(): string {
|
||||
$table = str_replace('Model', '', get_called_class());
|
||||
$tableIncludesADBSuffix = strpos($table, 'ADB') !== false;
|
||||
$table = str_replace('ADB', '', $table);
|
||||
return "`" . ($tableIncludesADBSuffix ? ADDRESSDB_DBNAME : FRONKDB_DBNAME) . "`.`" . $table . "`";
|
||||
}
|
||||
|
||||
private static function getDB() {
|
||||
protected static function getDB() {
|
||||
if (strpos(self::getFullyQualifiedTable(), ADDRESSDB_DBNAME) !== false)
|
||||
$FronkDB = FronkDB::singleton(ADDRESSDB_DBHOST, ADDRESSDB_DBUSER, ADDRESSDB_DBPASS, ADDRESSDB_DBNAME);
|
||||
else $FronkDB = FronkDB::singleton();
|
||||
|
||||
318
public/js/pages/AssetManagement/AssetManagement.js
Normal file
318
public/js/pages/AssetManagement/AssetManagement.js
Normal file
@@ -0,0 +1,318 @@
|
||||
// =================================================================================
|
||||
// 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');
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user