Files
thetool/application/AssetManagement/AssetManagementController.php
2025-07-11 08:55:11 +00:00

280 lines
12 KiB
PHP

<?php
class AssetManagementController extends TTCrud
{
protected string $headerTitle = 'Anlagenverwaltung';
protected bool $createText = false;
// Simplified columns for better layout, details are in the 'assetDetails' slot
protected array $columns = [
['key' => 'assetDetails', 'text' => 'Gerät', 'modal' => false, 'table' => ['filter' => 'search']],
['key' => 'currentUser', 'text' => 'Status', 'modal' => false, 'table' => ['sortable' => false, 'filter' => false]],
['key' => 'location', 'text' => 'Lagerort', 'required' => true, 'modal' => ['type' => 'text'], 'table' => ['filter' => 'search']],
['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 $additionalJSVariables = ['ASSET_ADMIN' => '1']; // Default to true
protected function prepareCrudConfig() {
// Restrict actions if the user does not have the 'AssetAdmin' permission.
if (!$this->user->can('AssetAdmin')) {
$this->additionalJSVariables['ASSET_ADMIN'] = '0';
$this->columns = array_filter($this->columns, fn($col) => !in_array($col['key'], ['actions', 'journal']));
}
}
/**
* This method is automatically called by the parent TTCrud->getByIdAction.
* It decodes the JSON string from the database into a PHP array.
*/
protected function getByIdParse($data) {
if (!empty($data['imageIds'])) {
$data['imageIds'] = json_decode($data['imageIds'], true);
} else {
$data['imageIds'] = [];
}
return $data;
}
protected function getAction()
{
$json = json_decode(file_get_contents('php://input'), true);
$pagination = $json['pagination'] ?? ['page' => 1, 'per_page' => 10];
$filters = $json['filters'] ?? [];
$order = $json['order'] ?? ['key' => 'id', 'order' => 'DESC'];
// Fetch paginated assets
$assets = AssetManagementModel::getAll($filters, $pagination['per_page'], ($pagination['page'] - 1) * $pagination['per_page'], $order);
$totalCount = AssetManagementModel::count($filters);
$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' => $pagination['per_page'], 'filtered_available' => 0]]);
return;
}
$latestJournalEntries = AssetManagementJournalModel::getLatestOpenEntries($assetIds);
// Fetch other related data
$reservations = AssetManagementReservationModel::getAll(['assetId' => $assetIds]);
$users = UserModel::search(['employee' => true]);
// Create maps for efficient lookup
$journalMap = array_reduce($latestJournalEntries, fn($carry, $item) => $carry + [$item->assetId => $item], []);
$userMap = array_reduce($users, fn($carry, $user) => $carry + [$user->id => $user->name], []);
$reservationMap = [];
foreach ($reservations as $res) {
if (!isset($reservationMap[$res->assetId])) $reservationMap[$res->assetId] = [];
$res->userName = $userMap[$res->userId] ?? 'Unbekannt';
$reservationMap[$res->assetId][] = $res;
}
$rows = [];
foreach ($assets as $asset) {
$row = (array)$asset;
// Decode imageIds for table view if needed, though not directly displayed, useful for logic
if (!empty($row['imageIds'])) {
$row['imageIds'] = json_decode($row['imageIds'], true);
} else {
$row['imageIds'] = [];
}
$latestJournal = $journalMap[$asset->id] ?? null;
// Determine current status based on the latest journal entry.
// If the latest entry has a returnDate, the asset is considered available.
if ($latestJournal && $latestJournal->returnDate === null) {
$row['journalId'] = $latestJournal->id;
$row['currentUser'] = $userMap[$latestJournal->userId] ?? 'Unbekannt';
$row['currentUserId'] = $latestJournal->userId;
$row['currentSite'] = $latestJournal->site;
$row['borrowDate'] = $latestJournal->borrowDate;
$row['expectedReturnDate'] = $latestJournal->expectedReturnDate;
$row['externalUser'] = $latestJournal->externalUser;
} else {
// Asset is available
$row['journalId'] = null;
$row['currentUser'] = null;
$row['currentUserId'] = null;
$row['currentSite'] = null;
$row['borrowDate'] = null;
$row['expectedReturnDate'] = null;
$row['externalUser'] = null;
}
$row['reservations'] = $reservationMap[$asset->id] ?? [];
$rows[] = $row;
}
self::returnJson([
'rows' => $rows,
'pagination' => [
'page' => $pagination['page'],
'per_page' => $pagination['per_page'],
'total_rows' => $totalCount,
'total_pages' => ceil($totalCount / $pagination['per_page']),
'filtered_available' => $totalCount
]
]);
}
protected function suggestAssetNumberAction()
{
$lastAsset = AssetManagementModel::getAll(['assetNumber' => 'XI%'], 1, 0, ['order' => 'DESC', 'key' => 'id'])[0] ?? null;
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'])) {
self::sendError("Alle erforderlichen Felder wurden nicht ausgefüllt.");
}
// Check for conflicting reservations if not forced
if (empty($post['force'])) {
$conflictingReservations = AssetManagementReservationModel::getConflictingReservations($post['assetId']);
if (!empty($conflictingReservations)) {
$res = $conflictingReservations[0];
$user = UserModel::getOne($res->userId);
self::returnJson([
'success' => false,
'warning' => 'conflict',
'message' => "Dieses Gerät ist für {$user->name} reserviert. Trotzdem ausleihen?"
]);
return;
}
}
AssetManagementJournalModel::create([
'assetId' => $post['assetId'],
'userId' => $post['userId'],
'site' => $post['site'],
'externalUser' => $post['externalUser'] ?? null,
'borrowReason' => $post['reason'] ?? null,
'expectedReturnDate' => $post['expectedReturnDate'] ?? null,
'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']);
$users = UserModel::search(['employee' => true]);
$userMap = array_reduce($users, fn($carry, $user) => $carry + [$user->id => $user->name], []);
foreach ($entries as $entry) {
$entry->userName = $userMap[$entry->userId] ?? 'Unbekannt';
}
self::returnJson($entries);
}
protected function uploadFileAction()
{
$file = $_FILES['file'] ?? null;
if (!$file || $file['error'] !== UPLOAD_ERR_OK) {
self::returnJson(['error' => 'File upload failed']);
return;
}
try {
$uploaded = mfUpload::handleFormUpload("file", false, "/AssetManagement");
self::returnJson(['success' => true, 'fileId' => $uploaded->id]);
} catch (Exception $e) {
self::returnJson(['error' => 'Upload error: ' . $e->getMessage()]);
}
}
protected function deleteAssetImageAction() {
$post = json_decode(file_get_contents('php://input'), true);
if (empty($post['assetId']) || empty($post['imageId'])) {
self::sendError("Asset ID or Image ID is missing.");
}
$asset = AssetManagementModel::get($post['assetId']);
if (!$asset) self::sendError("Asset not found.");
$imageIds = !empty($asset->imageIds) ? json_decode($asset->imageIds, true) : [];
// Remove the imageId
$imageIds = array_filter($imageIds, fn($id) => $id != $post['imageId']);
$asset->imageIds = json_encode(array_values($imageIds)); // Re-index array
// If the deleted image was the main image, set main image to the first available image or null
if ($asset->mainImageId == $post['imageId']) {
$asset->mainImageId = !empty($imageIds) ? $imageIds[0] : null;
}
AssetManagementModel::update((array)$asset);
// Optional: Delete the actual file from storage
// mfUpload::delete($post['imageId']);
self::returnJson(['success' => true, 'message' => 'Image deleted.', 'asset' => $this->getByIdParse((array)$asset)]);
}
protected function getReservationsAction() {
if (empty($this->request->assetId)) self::sendError("Asset ID fehlt.");
$reservations = AssetManagementReservationModel::getAll(['assetId' => $this->request->assetId], null, 0, ['key' => 'startDate', 'order' => 'ASC']);
$users = UserModel::search(['employee' => true]);
$userMap = array_reduce($users, fn($carry, $user) => $carry + [$user->id => $user->name], []);
foreach ($reservations as $res) {
$res->userName = $userMap[$res->userId] ?? 'Unbekannt';
}
self::returnJson($reservations);
}
protected function createReservationAction() {
$post = json_decode(file_get_contents('php://input'), true);
if (empty($post['assetId']) || empty($post['userId']) || empty($post['startDate'])) {
self::sendError("Alle erforderlichen Felder sind nicht ausgefüllt.");
}
AssetManagementReservationModel::create([
'assetId' => $post['assetId'],
'userId' => $post['userId'],
'startDate' => $post['startDate'],
'endDate' => $post['endDate'] ?? null,
'notes' => $post['notes'] ?? null,
'createBy' => $this->user->id,
'create' => time()
]);
self::returnJson(['success' => true, 'message' => 'Reservierung erfolgreich erstellt.']);
}
protected function deleteReservationAction() {
$post = json_decode(file_get_contents('php://input'), true);
if (empty($post['id'])) self::sendError("Reservierungs-ID fehlt.");
AssetManagementReservationModel::delete($post['id']);
self::returnJson(['success' => true, 'message' => 'Reservierung gelöscht.']);
}
}