'status', 'text' => 'Status', 'modal' => false, 'table' => ['filter' => 'iconSelect', 'filterOptions' => [ ['value' => 'new', 'text' => 'Neu', 'icon' => 'fas fa-star text-primary'], ['value' => 'assigned', 'text' => 'Zugewiesen', 'icon' => 'fas fa-user-check text-info'], ['value' => 'scheduled', 'text' => 'Geplant', 'icon' => 'fas fa-calendar-check text-warning'], ['value' => 'in_progress', 'text' => 'In Bearbeitung', 'icon' => 'fas fa-cog text-warning'], ['value' => 'documented', 'text' => 'Dokumentiert', 'icon' => 'fas fa-file-alt text-success'], ['value' => 'completed', 'text' => 'Abgeschlossen', 'icon' => 'fas fa-check-double text-secondary'], ['value' => 'cancelled', 'text' => 'Abgebrochen', 'icon' => 'fas fa-ban text-danger'], ['value' => 'archived', 'text' => 'Archiviert', 'icon' => 'fas fa-archive text-muted'], ]] ]; protected array $additionalJS = ["js/pages/WorkorderMphBase/WorkorderMphBase.js"]; protected array $additionalHead = [""]; protected function getStatusText(string $statusKey): string { $statusMap = array_column($this->statusColumn['table']['filterOptions'] ?? [], 'text', 'value'); return $statusMap[$statusKey] ?? ucfirst(str_replace('_', ' ', $statusKey)); } //region SHARED ACTIONS /** * Fetches documentation and journal entries for a given workorder. */ protected function getDocumentationAction() { if (empty($this->request->workorderMphId)) self::sendError("Arbeitsauftrags-ID fehlt."); $docs = WorkorderMphDocumentationModel::getAll(['workorderMphId' => intval($this->request->workorderMphId)], null, 0, ['key' => 'create', 'order' => 'ASC']); $journals = WorkorderMphJournalModel::getAll(['workorderMphId' => intval($this->request->workorderMphId)], null, 0, ['key' => 'create', 'order' => 'DESC']); $responseDocs = []; $typeCounts = []; foreach ($docs as $doc) { $file = new File($doc->fileId); $documentTypeKey = $doc->documentType; $typeCounts[$documentTypeKey] = ($typeCounts[$documentTypeKey] ?? 0) + 1; $originalFilename = $file->orig_filename ?? $file->filename; $extension = pathinfo($originalFilename, PATHINFO_EXTENSION); $newFilename = "{$documentTypeKey}_{$typeCounts[$documentTypeKey]}." . strtolower($extension); $responseDocs[] = [ 'id' => $doc->id, 'fileId' => $doc->fileId, 'fileName' => $newFilename, 'description' => $doc->description, 'documentType' => $documentTypeKey, 'userName' => UserModel::getOne($doc->createBy)->name ?? 'Unbekannt', 'mimetype' => $file->mimetype ?? 'application/octet-stream', 'create' => $doc->create ]; } foreach ($journals as $journal) { $journal->createByName = UserModel::getOne($journal->createBy)->name ?? 'Unbekannt'; } self::returnJson(['docs' => $responseDocs, 'journals' => $journals]); } /** * Adds a new entry to a workorder's journal. */ protected function addJournalAction() { $post = json_decode(file_get_contents('php://input'), true); if (empty($post['workorderMphId']) || empty(trim($post['text']))) self::sendError("Arbeitsauftrags-ID und Text sind erforderlich."); WorkorderMphJournalModel::create([ 'workorderMphId' => $post['workorderMphId'], 'text' => $post['text'], 'createBy' => $this->user->id, 'create' => time() ]); $journals = WorkorderMphJournalModel::getAll(['workorderMphId' => intval($post['workorderMphId'])], null, 0, ['key' => 'create', 'order' => 'DESC']); foreach ($journals as $journal) { $journal->createByName = UserModel::getOne($journal->createBy)->name ?? 'Unbekannt'; } self::returnJson(['success' => true, 'message' => 'Journaleintrag hinzugefügt.', 'journals' => $journals]); } /** * Updates the additional info field for a workorder. */ protected function updateAdditionalInfoAction() { $post = json_decode(file_get_contents('php://input'), true); if (empty($post['workorderMphId'])) self::sendError("Arbeitsauftrags-ID fehlt."); $workorder = WorkorderMphModel::get($post['workorderMphId']); if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden."); $oldInfo = $workorder->additionalInfo; $newInfo = $post['additionalInfo'] ?? null; $workorder->additionalInfo = $newInfo; WorkorderMphModel::update((array)$workorder); WorkorderMphJournalModel::create([ 'workorderMphId' => $workorder->id, 'text' => "Zusatzinfo geändert.\nAlt: '{$oldInfo}'\nNeu: '{$newInfo}'", 'create' => time(), 'createBy' => $this->user->id, ]); self::returnJson(['success' => true, 'message' => 'Zusatzinfo aktualisiert.', 'newInfo' => $newInfo]); } /** * Get all Wohneinheiten for a specific workorder with their statuses and notes */ protected function getWohneinheitenAction() { if (empty($this->request->workorderMphId)) self::sendError("Arbeitsauftrags-ID fehlt."); $workorderMphId = intval($this->request->workorderMphId); $workorder = WorkorderMphModel::get($workorderMphId); if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden."); // Get all Wohneinheiten for this Hausnummer from addressdb $db = FronkDB::singleton(ADDRESSDB_DBHOST, ADDRESSDB_DBUSER, ADDRESSDB_DBPASS, ADDRESSDB_DBNAME); $hausnummerId = $db->escape($workorder->hausnummerId); // Fetch statuses from addressdb $statusSql = "SELECT id, code, name FROM Status WHERE type = 'wohneinheit' ORDER BY code ASC"; $statusResult = $db->query($statusSql); $statuses = $statusResult ? $statusResult->fetch_all(MYSQLI_ASSOC) : []; $statusOptions = array_map(function($s) { return ['value' => intval($s['id']), 'text' => $s['code'] . ' - ' . $s['name'], 'code' => intval($s['code'])]; }, $statuses); // Fetch Wohneinheiten directly $sql = "SELECT w.id, w.zusatz, w.tuer, w.contact, w.oaid, w.note, w.status_id, w.splice_hak_completed FROM Wohneinheit w WHERE w.hausnummer_id = $hausnummerId ORDER BY w.zusatz"; $result = $db->query($sql); $wohneinheiten = $result ? $result->fetch_all(MYSQLI_ASSOC) : []; // Get Preorders for this Hausnummer to fallback contact info $preorders = []; if (class_exists('PreorderModel')) { $preorderList = PreorderModel::search(['adb_hausnummer_id' => $workorder->hausnummerId, 'deleted' => 0]); foreach ($preorderList as $preorder) { if ($preorder->adb_wohneinheit_id) { $preorders[$preorder->adb_wohneinheit_id] = $preorder; } } } // Merge data $response = []; foreach ($wohneinheiten as $we) { // Contact info logic $contact = $we['contact']; $preorderContact = null; $preorderUcode = null; if (isset($preorders[$we['id']])) { $p = $preorders[$we['id']]; $preorderUcode = $p->ucode; $pContact = trim($p->firstname . ' ' . $p->lastname); if ($p->phone) $pContact .= ' (' . $p->phone . ')'; $preorderContact = $pContact; // If address contact is empty, use preorder contact if (empty($contact)) { $contact = $pContact; } } $response[] = [ 'wohneinheitId' => intval($we['id']), 'zusatz' => $we['zusatz'], 'tuer' => $we['tuer'], 'contact' => $contact, 'preorderContact' => $preorderContact, 'preorderUcode' => $preorderUcode, 'oaid' => $we['oaid'], 'status' => intval($we['status_id']), 'spliceCompleted' => intval($we['splice_hak_completed'] ?? 0), 'note' => $we['note'], ]; } self::returnJson([ 'wohneinheiten' => $response, 'statusOptions' => $statusOptions, 'hausnummerId' => $workorder->hausnummerId ]); } /** * Update status and note for a specific Wohneinheit */ protected function updateWohneinheitAction() { $post = json_decode(file_get_contents('php://input'), true); if (empty($post['workorderMphId']) || empty($post['wohneinheitId'])) { self::sendError("Arbeitsauftrags-ID und Wohneinheit-ID sind erforderlich."); } $workorderMphId = intval($post['workorderMphId']); $wohneinheitId = intval($post['wohneinheitId']); $newStatusId = intval($post['status'] ?? 1); $spliceCompleted = isset($post['spliceCompleted']) ? intval($post['spliceCompleted']) : 0; $tuer = $post['tuer'] ?? null; $zusatz = $post['zusatz'] ?? null; $db = FronkDB::singleton(ADDRESSDB_DBHOST, ADDRESSDB_DBUSER, ADDRESSDB_DBPASS, ADDRESSDB_DBNAME); $escapedWohneinheitId = $db->escape($wohneinheitId); // Fetch current state $currentSql = "SELECT status_id, tuer, zusatz, splice_hak_completed FROM Wohneinheit WHERE id = $escapedWohneinheitId"; $result = $db->query($currentSql); $current = $result ? $result->fetch_assoc() : null; if (!$current) self::sendError("Wohneinheit nicht gefunden."); $oldStatusId = intval($current['status_id']); $oldTuer = $current['tuer']; $oldZusatz = $current['zusatz']; $oldSplice = intval($current['splice_hak_completed'] ?? 0); // Update Wohneinheit $escapedTuer = $tuer !== null ? "'" . $db->escape($tuer) . "'" : "NULL"; $escapedZusatz = $zusatz !== null ? "'" . $db->escape($zusatz) . "'" : "NULL"; $escapedStatusId = $db->escape($newStatusId); $escapedSplice = $db->escape($spliceCompleted); $updateSql = "UPDATE Wohneinheit SET status_id = $escapedStatusId, tuer = $escapedTuer, zusatz = $escapedZusatz, splice_hak_completed = $escapedSplice WHERE id = $escapedWohneinheitId"; $db->query($updateSql); // Journaling $changes = []; if ($oldStatusId !== $newStatusId) { // Fetch status names for better logging $statusNamesSql = "SELECT id, code, name FROM Status WHERE id IN ($oldStatusId, $newStatusId)"; $statusRes = $db->query($statusNamesSql); $statusMap = []; if ($statusRes) { while($row = $statusRes->fetch_assoc()) { $statusMap[$row['id']] = $row['code'] . ' - ' . $row['name']; } } $oldText = $statusMap[$oldStatusId] ?? "ID $oldStatusId"; $newText = $statusMap[$newStatusId] ?? "ID $newStatusId"; $changes[] = "Status: $oldText → $newText"; } if ($oldSplice !== $spliceCompleted) { $changes[] = "Spleiß: " . ($spliceCompleted ? 'Erledigt' : 'Nicht erledigt'); } if ($oldTuer !== $tuer) { $changes[] = "Tür aktualisiert: '$oldTuer' -> '$tuer'"; } if ($oldZusatz !== $zusatz) { $changes[] = "Zusatz aktualisiert: '$oldZusatz' -> '$zusatz'"; } if (!empty($changes)) { WorkorderMphJournalModel::create([ 'workorderMphId' => $workorderMphId, 'text' => "Wohneinheit $wohneinheitId: " . implode(', ', $changes), 'create' => time(), 'createBy' => $this->user->id, ]); } // Status flag logic for BEP MD (241) and ONT (300). Need to check codes for these IDs. // Since we only have IDs, we need to check the code of the newStatusId. $newStatusCodeSql = "SELECT code FROM Status WHERE id = $escapedStatusId"; $resCode = $db->query($newStatusCodeSql); $newStatusCode = $resCode ? intval($resCode->fetch_assoc()['code']) : 0; if (in_array($newStatusCode, [241, 300])) { // 241=BEP MD, 300=ONT $this->setWohneinheitStatusflag($wohneinheitId, 200); } self::returnJson(['success' => true, 'message' => 'Wohneinheit aktualisiert.']); } /** * Set statusflag on Wohneinheit in addressdb */ private function setWohneinheitStatusflag(int $wohneinheitId, int $statusflagId) { $db = FronkDB::singleton(ADDRESSDB_DBHOST, ADDRESSDB_DBUSER, ADDRESSDB_DBPASS, ADDRESSDB_DBNAME); $weId = $db->escape($wohneinheitId); $sfId = $db->escape($statusflagId); // Check if statusflag already exists $checkSql = "SELECT COUNT(*) as count FROM WohneinheitStatusflagValue WHERE wohneinheit_id = $weId AND statusflag_id = $sfId"; $result = $db->query($checkSql); $exists = $result->fetch_assoc()['count'] > 0; if (!$exists) { $insertSql = "INSERT INTO WohneinheitStatusflagValue (wohneinheit_id, statusflag_id, create, createBy) VALUES ($weId, $sfId, " . time() . ", " . $this->user->id . ")"; $db->query($insertSql); } } /** * Update checkbox documentation fields */ protected function updateCheckboxesAction() { $post = json_decode(file_get_contents('php://input'), true); if (empty($post['workorderMphId'])) self::sendError("Arbeitsauftrags-ID fehlt."); $workorder = WorkorderMphModel::get($post['workorderMphId']); if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden."); $changes = []; $checkboxFields = ['easement', 'btb', 'fttxLocationSupplied', 'conduitToHuepLaid', 'huepMounted', 'dropCableAvailable', 'spliceCompleted']; $updateHausnummerStatus = false; foreach ($checkboxFields as $field) { if (array_key_exists($field, $post)) { $oldValue = $workorder->$field; $newValue = $post[$field] ? 1 : 0; if ($oldValue !== $newValue) { $workorder->$field = $newValue; $changes[] = "$field: " . ($newValue ? 'ja' : 'nein'); // Check for FTTx Location mit Leerrohr versorgt if ($field === 'fttxLocationSupplied' && $newValue === 1) { $updateHausnummerStatus = true; } } } } if (!empty($changes)) { WorkorderMphModel::update((array)$workorder); if ($updateHausnummerStatus) { $db = FronkDB::singleton(ADDRESSDB_DBHOST, ADDRESSDB_DBUSER, ADDRESSDB_DBPASS, ADDRESSDB_DBNAME); // Find status ID for code 200 $statusSql = "SELECT id FROM Status WHERE code = 200 AND type = 'hausnummer' LIMIT 1"; $statusResult = $db->query($statusSql); if ($statusResult && $row = $statusResult->fetch_assoc()) { $statusId = $row['id']; $hnId = $db->escape($workorder->hausnummerId); $updateHnSql = "UPDATE Hausnummer SET status_id = $statusId WHERE id = $hnId"; $db->query($updateHnSql); } } WorkorderMphJournalModel::create([ 'workorderMphId' => $workorder->id, 'text' => "Dokumentation aktualisiert:\n" . implode("\n", $changes), 'create' => time(), 'createBy' => $this->user->id, ]); } self::returnJson(['success' => true, 'message' => 'Dokumentation aktualisiert.']); } //endregion }