added new features for asset mgmt

This commit is contained in:
Luca Haid
2025-06-26 15:23:57 +02:00
parent 97f8a625e4
commit b0d7d4aeeb
10 changed files with 159 additions and 27 deletions

View File

@@ -482,6 +482,15 @@ $siteTitle = "Benutzer";
<label for="can_ADBExtended" class="form-check-label">Address-DB erweitert</label>
</div>
</div>
<div class="col-4">
<div class="form-group form-check">
<input type="checkbox" class="form-check-input" name="can[AssetAdmin]"
id="can_AssetAdmin"
value="1" <?=($user && $user->can("AssetAdmin")) ? "checked='checked'" : ""?> />
<label for="can_AssetAdmin" class="form-check-label">Asset-Admin</label>
</div>
</div>
</div>
<hr/>

View File

@@ -117,6 +117,7 @@
<?php endif; ?>
<?php if($me->is(["Admin"])): ?><li class="border-top"><a href="<?=self::getUrl("MailtemplateDispatch")?>"><i class="far fa-fw fa-envelope text-info"></i> Emailaussendungen</a></li><?php endif; ?>
<?php if($me->is(["Admin"])): ?><li class="border-top"><a href="<?=self::getUrl("AssetManagement")?>"><i class="far fa-fw fa-clipboard-list text-info"></i> Asset Management</a></li><?php endif; ?>
</ul>
</li>
<?php endif; ?>

View File

@@ -4,26 +4,47 @@ class AssetManagementController extends TTCrud
{
protected string $headerTitle = 'Anlagenverwaltung';
protected string $singleText = 'Anlage';
protected bool $createText = false;
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' => 'currentUser', 'text' => 'Akt. Mitarbeiter', 'modal' => false, 'table' => ['sortable' => false, 'filter' => false]],
['key' => 'currentSite', 'text' => 'Akt. Baustelle', 'modal' => false, 'table' => ['sortable' => false, 'filter' => false]],
['key' => 'borrowDate', 'text' => 'Ausgeliehen seit', 'modal' => false, 'table' => ['sortable' => false, 'filter' => false]],
['key' => 'location', 'text' => 'Lagerort', 'required' => true, 'modal' => ['type' => 'text']],
['key' => 'serviceDueDate', 'text' => 'Service fällig', 'required' => false, 'modal' => ['type' => 'datepicker']],
['key' => 'description', 'text' => 'Beschreibung', 'modal' => ['type' => 'text'], 'table' => false],
['key' => 'serviceDueDate', 'text' => 'Service fällig', 'required' => false, 'modal' => ['type' => 'date'], 'table' => ['filter' => 'date']],
['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 array $additionalJSVariables = ['ASSET_ADMIN' => true];
protected function prepareCrudConfig() {
if ($this->user->can('AssetAdmin')) return;
$this->columns = array_filter($this->columns, function ($column) {
return $column['key'] != 'actions';
});
$this->additionalJSVariables['ASSET_ADMIN'] = false;
}
protected function getAction()
{
$filter = $this->postData['filters'] ?? [];
$order = $this->postData['order'] ?? ['key' => null, 'order' => 'ASC'];
$page = $this->postData['pagination']['page'] ?? 1;
$perPage = $this->postData['pagination']['per_page'] ?? 10;
if ($order['key'] === null && isset($this->defaultOrder)) {
$order = $this->defaultOrder;
}
$json = json_decode(file_get_contents('php://input'), true);
$assets = AssetManagementModel::getAll([], $this->request->order);
$assets = AssetManagementModel::getAll($filter, $perPage, ($page - 1) * $perPage, $order);
$assetIds = array_map(fn($asset) => $asset->id, $assets);
if (empty($assetIds)) {
@@ -79,7 +100,7 @@ class AssetManagementController extends TTCrud
protected function suggestAssetNumberAction()
{
$lastAsset = AssetManagementModel::getOne([], ['order' => 'DESC', 'key' => 'id']);
$lastAsset = AssetManagementModel::getAll(['assetNumber' => 'XI%'],1,0, ['order' => 'DESC', 'key' => 'id'])[0];
if (!$lastAsset || !preg_match('/XI(\d+)/', $lastAsset->assetNumber, $matches)) {
$nextNumber = 1;
} else {
@@ -92,7 +113,7 @@ class AssetManagementController extends TTCrud
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'])) {
if (empty($post['assetId']) || empty($post['userId']) || empty($post['site'])) {
self::sendError("Alle Felder sind erforderlich.");
}

View File

@@ -3,6 +3,7 @@
class AssetManagementModel extends TTCrudBaseModel {
public int $id;
public string $name;
public ?string $description;
public string $assetNumber;
public string $location;
public ?string $serviceDueDate;

View File

@@ -7,7 +7,7 @@ class AssetManagementJournalModel extends TTCrudBaseModel {
public string $site;
public int $borrowDate;
public ?int $returnDate;
public string $borrowReason;
public ?string $borrowReason;
public ?string $returnReason;
public int $createBy;
public int $create;

View File

@@ -263,6 +263,7 @@ class UserController extends mfBaseController
$user->permissions->canWarehouseEShop = "false";
$user->permissions->canWarehouseUser = "false";
$user->permissions->canADBExtended = "false";
$user->permissions->canAssetAdmin = "false";
if($r->get("can") && is_array($r->can)) {
foreach($r->can as $key => $can) {

View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class AddDescriptionToAssetManagement extends AbstractMigration
{
/**
* Adds the 'description' column to the 'AssetManagement' table.
*/
public function up(): void
{
$table = $this->table('AssetManagement');
$table->addColumn('description', 'text', [
'null' => true,
'after' => 'name',
]);
$table->update();
}
/**
* Removes the 'description' column from the 'AssetManagement' table.
*/
public function down(): void
{
$table = $this->table('AssetManagement');
$table->removeColumn('description');
$table->update();
}
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class ModifyBorrowReasonInAssetManagementJournal extends AbstractMigration
{
/**
* Changes the 'borrowReason' column to be nullable, adds a comment, and sets its position.
*/
public function up(): void
{
$table = $this->table('AssetManagementJournal');
$table->changeColumn('borrowReason', 'text', [
'null' => true,
'comment' => 'Reason for borrowing the asset',
'after' => 'returnDate',
]);
$table->update();
}
/**
* Reverts the 'borrowReason' column to its previous state (not nullable and without a comment).
*/
public function down(): void
{
// Reverting the changes made in the up() method.
// This assumes the column was NOT NULL and had no comment previously.
$table = $this->table('AssetManagementJournal');
$table->changeColumn('borrowReason', 'text', [
'null' => false,
'comment' => 'Reason for borrowing the asset', // Set comment back to empty
]);
$table->update();
}
}

View File

@@ -0,0 +1,30 @@
<?php /** @noinspection ALL */
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class WorkerPermissionAddCanAssetAdmin extends AbstractMigration {
public function up(): void {
if ($this->getEnvironment() == "thetool") {
$table = $this->table("WorkerPermission");
$table->addColumn("canAssetAdmin", "enum", ["null" => false, "values" => 'false,true', "default" => "false", "after" => "canSuperexpert"]);
$table->update();
}
if ($this->getEnvironment() == "addressdb") {
}
}
public function down(): void {
if ($this->getEnvironment() == "thetool") {
$table = $this->table("WorkerPermission");
$table->removeColumn("canAssetAdmin");
$table->save();
}
if ($this->getEnvironment() == "addressdb") {
}
}
}

View File

@@ -7,21 +7,28 @@ Vue.component('asset-management', {
<asset-management-modal
v-if="modalId"
:id="modalId"
@close="modalId = null; $refs.table.refreshTable()"/>
@close="modalId = null; $refs.table.$refs.table.refreshTable()"/>
<asset-management-journal-modal
v-if="journalModalAssetId"
:asset-id="journalModalAssetId"
@close="journalModalAssetId = null"/>
<button @click="modalId = 'create'" class="btn btn-primary">Anlage erstellen</button>
<tt-table-crud
ref="table"
@edit="modalId = $event.id"
@edit="window.console.log($event.id);modalId = $event.id"
emit-edit
:crud-config="crudConfig">
<template v-slot:currentuser="{ row }">
<asset-borrow-return-widget
v-if="window.TT_CONFIG.ASSET_ADMIN === '1'"
:row-data="row"
@update="$refs.table.refreshTable()"/>
@update="$refs.table.$refs.table.refreshTable()"/>
<span v-else>
{{ row.currentUser || 'Nicht ausgeliehen' }}
</span>
</template>
<template v-slot:journal="{ row }">
@@ -40,7 +47,7 @@ Vue.component('asset-management', {
<template v-slot:borrowdate="{ row }">
<span v-if="row.borrowDate">
{{ window.moment.unix(row.borrowDate).format('DD.MM.YYYY') }}
{{ window.moment.unix(row.borrowDate).format('DD.MM.YYYY HH:mm') }}
</span>
</template>
@@ -91,7 +98,7 @@ Vue.component('asset-borrow-return-widget', {
<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-textarea label="Grund (optional)" 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">
@@ -126,8 +133,8 @@ Vue.component('asset-borrow-return-widget', {
this.showBorrowModal = true;
},
async borrowAsset() {
if (!this.borrowSite || !this.borrowReason) {
return window.notify('error', 'Bitte Baustelle und Grund angeben.');
if (!this.borrowSite) {
return window.notify('error', 'Bitte Baustelle/Projekt angeben.');
}
try {
const response = await axios.post(`${window.TT_CONFIG.BASE_PATH}/AssetManagement/borrow`, {
@@ -183,23 +190,18 @@ Vue.component('asset-management-modal', {
@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="Kennzeichen / Nr." v-model="asset.assetNumber" sm row required/>
<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-textarea label="Beschreibung" v-model="asset.description" sm row/>
</tt-modal>
`,
data() {
data(){
return {
asset: {
name: '',
assetNumber: '',
location: 'Hauptlager',
location: 'Liftkammer',
serviceDueDate: null
},
}
@@ -210,6 +212,7 @@ Vue.component('asset-management-modal', {
}
},
async mounted() {
console.log('AssetManagementModal mounted with id:', this.id, this.isCreateMode);
if (!this.isCreateMode) {
const response = await axios.get(`${window.TT_CONFIG.BASE_PATH}/AssetManagement/getById`, { params: { id: this.id } });
this.asset = response.data;