added new stuff for asset management
This commit is contained in:
@@ -3,104 +3,104 @@
|
||||
class AssetManagementController extends TTCrud
|
||||
{
|
||||
protected string $headerTitle = 'Anlagenverwaltung';
|
||||
protected string $singleText = 'Anlage';
|
||||
protected bool $createText = false;
|
||||
|
||||
// Simplified columns for better layout, details are in the 'assetDetails' slot
|
||||
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, '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' => 'description', 'text' => 'Beschreibung', 'modal' => ['type' => 'text'], 'table' => false],
|
||||
['key' => 'assetDetails', 'text' => 'Gerät', 'modal' => false, 'table' => ['sortable' => false, 'filter' => ['type' => 'search', 'key' => 'name|assetNumber|description']]],
|
||||
['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' => true];
|
||||
protected array $additionalJSVariables = ['ASSET_ADMIN' => '1']; // Default to 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;
|
||||
// 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']));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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($filter, $perPage, ($page - 1) * $perPage, $order);
|
||||
$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' => 10, 'filtered_available' => 0]]);
|
||||
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);
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch other related data
|
||||
$reservations = AssetManagementReservationModel::getAll(['assetId' => $assetIds]);
|
||||
$users = UserModel::search(['employee' => true]);
|
||||
$userMap = array_reduce($users, function ($carry, $user) {
|
||||
$carry[$user->id] = $user->name;
|
||||
return $carry;
|
||||
}, []);
|
||||
|
||||
// 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;
|
||||
$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;
|
||||
// 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;
|
||||
} else {
|
||||
// Asset is available
|
||||
$row['journalId'] = null;
|
||||
$row['currentUser'] = null;
|
||||
$row['currentUserId'] = null;
|
||||
$row['currentSite'] = null;
|
||||
$row['borrowDate'] = null;
|
||||
$row['expectedReturnDate'] = null;
|
||||
}
|
||||
|
||||
$row['reservations'] = $reservationMap[$asset->id] ?? [];
|
||||
$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,
|
||||
'rows' => $rows,
|
||||
'pagination' => [
|
||||
'page' => $pagination['page'],
|
||||
'per_page' => $pagination['per_page'],
|
||||
'total_rows' => $totalRows,
|
||||
'total_pages' => ceil($totalRows / $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];
|
||||
$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 {
|
||||
@@ -114,14 +114,35 @@ class AssetManagementController extends TTCrud
|
||||
{
|
||||
$post = json_decode(file_get_contents('php://input'), true);
|
||||
if (empty($post['assetId']) || empty($post['userId']) || empty($post['site'])) {
|
||||
self::sendError("Alle Felder sind erforderlich.");
|
||||
self::sendError("Alle erforderlichen Felder wurden nicht ausgefüllt.");
|
||||
}
|
||||
|
||||
// Check for conflicting reservations if not forced
|
||||
if (empty($post['force'])) {
|
||||
$now = time();
|
||||
$conflictingReservations = AssetManagementReservationModel::dbSelect("
|
||||
SELECT * FROM AssetManagementReservation
|
||||
WHERE assetId = {$post['assetId']} AND startDate <= $now AND (endDate IS NULL OR endDate >= $now)
|
||||
");
|
||||
|
||||
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'],
|
||||
'borrowReason' => $post['reason'],
|
||||
'borrowReason' => $post['reason'] ?? null,
|
||||
'expectedReturnDate' => $post['expectedReturnDate'] ?? null,
|
||||
'borrowDate' => time(),
|
||||
'createBy' => $this->user->id,
|
||||
'create' => time(),
|
||||
@@ -133,14 +154,10 @@ class AssetManagementController extends TTCrud
|
||||
protected function returnAction()
|
||||
{
|
||||
$post = json_decode(file_get_contents('php://input'), true);
|
||||
if (empty($post['journalId'])) {
|
||||
self::sendError("Journal-Eintrag nicht gefunden.");
|
||||
}
|
||||
if (empty($post['journalId'])) self::sendError("Journal-Eintrag nicht gefunden.");
|
||||
|
||||
$journalEntry = AssetManagementJournalModel::get($post['journalId']);
|
||||
if (!$journalEntry) {
|
||||
self::sendError("Journal-Eintrag nicht gefunden.");
|
||||
}
|
||||
if (!$journalEntry) self::sendError("Journal-Eintrag nicht gefunden.");
|
||||
|
||||
$journalEntry->returnDate = time();
|
||||
$journalEntry->returnReason = $post['reason'] ?? 'Zurückgegeben';
|
||||
@@ -153,18 +170,64 @@ class AssetManagementController extends TTCrud
|
||||
{
|
||||
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;
|
||||
}, []);
|
||||
$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 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.']);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user