'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' => '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) => $col['key'] !== 'actions'); } } /** * 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.']); } protected function printLabelAction() { if (!$this->user->can('AssetAdmin')) { self::sendError("Permission denied", 403); } $assetId = $this->request->id; $size = $this->request->size ?? '25'; // Default to 25mm $asset = AssetManagementModel::get($assetId); if (!$asset) { self::sendError("Asset not found", 404); } $pdf_vars = [ 'companyAddress' => 'Fladnitz 150, 8322 Studenzen', 'companyPhone' => '+43 3115 40800', 'invNumber' => $asset->assetNumber, 'size' => $size ]; $pdf = new PdfForm("AssetManagement/LABEL", $pdf_vars); if ($size == '50') { $wkhtmltopdfArgs = "--page-height 50mm --page-width 80mm --margin-top 1mm --margin-bottom 0 --margin-left 0 --margin-right 0 --disable-smart-shrinking --encoding utf-8"; } else { // 25mm $wkhtmltopdfArgs = "--page-height 25mm --page-width 50mm --margin-top 1mm --margin-bottom 0 --margin-left 0 --margin-right 0 --disable-smart-shrinking --encoding utf-8"; } $filename = $pdf->render($wkhtmltopdfArgs); header('Content-Type: application/pdf'); header('Content-Disposition: inline; filename="label-' . $asset->assetNumber . '.pdf"'); readfile($filename); die(); } }