Files
thetool/application/WorkorderMphBase/WorkorderMphBaseController.php
2025-12-13 21:27:43 +00:00

566 lines
24 KiB
PHP

<?php
class WorkorderMphBaseController extends TTCrud
{
protected array $statusColumn = [
'key' => 'status', 'text' => 'Status', 'modal' => false, 'table' => ['filter' => 'iconSelect', 'sortable' => false, '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 = ["<link rel='stylesheet' href='/js/pages/WorkorderMphBase/WorkorderMphBase.css'>"];
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]);
}
/**
* Upload documentation for the Workorder itself (not Wohneinheit).
*/
protected function uploadDocumentationAction()
{
if (empty($_FILES['files']) && empty($_FILES['file'])) self::sendError('Erforderliche Daten fehlen.');
if (empty($_POST['workorderMphId'])) self::sendError('Workorder ID fehlt.');
$workorderMphId = intval($_POST['workorderMphId']);
$uploadedCount = 0;
// Handle multiple files (files[])
if (!empty($_FILES['files'])) {
foreach ($_FILES['files']['name'] as $index => $name) {
if ($_FILES['files']['error'][$index] === UPLOAD_ERR_OK) {
// Mock the $_FILES entry for handleFormUpload
$_FILES['single_upload_file'] = [
'name' => $name,
'type' => $_FILES['files']['type'][$index],
'tmp_name' => $_FILES['files']['tmp_name'][$index],
'error' => $_FILES['files']['error'][$index],
'size' => $_FILES['files']['size'][$index]
];
try {
$uploaded = mfUpload::handleFormUpload("single_upload_file", false, "/WorkorderMph");
WorkorderMphDocumentationModel::create([
'workorderMphId' => $workorderMphId,
'fileId' => $uploaded->id,
'description' => $_POST['description'] ?? '',
'documentType' => $_POST['documentType'] ?? 'other',
'create' => time(),
'createBy' => $this->user->id
]);
$uploadedCount++;
} catch (Exception $e) {
// Log error
}
}
}
}
// Handle single file (file) - fallback or primary if JS sends single
elseif (!empty($_FILES['file'])) {
try {
$uploaded = mfUpload::handleFormUpload("file", false, "/WorkorderMph");
WorkorderMphDocumentationModel::create([
'workorderMphId' => $workorderMphId,
'fileId' => $uploaded->id,
'description' => $_POST['description'] ?? '',
'documentType' => $_POST['documentType'] ?? 'other',
'create' => time(),
'createBy' => $this->user->id
]);
$uploadedCount++;
} catch (Exception $e) {
self::sendError("Upload fehlgeschlagen: " . $e->getMessage());
}
}
if ($uploadedCount > 0) {
self::returnJson(['success' => true, 'message' => "$uploadedCount Datei(en) erfolgreich hochgeladen."]);
} else {
self::sendError("Keine Dateien wurden hochgeladen.");
}
}
/**
* Delete Workorder documentation
*/
protected function deleteDocumentationAction()
{
if (empty($this->postData['documentationId'])) self::sendError("Dokumentations-ID fehlt.");
WorkorderMphDocumentationModel::delete($this->postData['documentationId']);
self::returnJson(['success' => true, 'message' => 'Dokument gelöscht.']);
}
/**
* 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.oaid ASC";
$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')) {
// Use searchActive to filter out canceled preorders (status_code = 20)
$preorderList = PreorderModel::searchActive(['adb_hausnummer_id' => $workorder->hausnummerId]);
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;
}
}
// Get document count for this Wohneinheit
$docCountSql = "SELECT COUNT(*) as cnt FROM WohneinheitDocumentation WHERE wohneinheit_id = " . $db->escape($we['id']);
$docCountResult = $db->query($docCountSql);
$documentCount = 0;
if ($docCountResult) {
$docCountRow = $docCountResult->fetch_assoc();
$documentCount = intval($docCountRow['cnt']);
}
$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'],
'documentCount' => $documentCount,
];
}
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;
// Validate that "Tür" field is not empty if it's being set
if ($tuer !== null && trim($tuer) === '') {
self::sendError("Das Feld 'Tür' darf nicht leer sein.");
}
$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);
}
}
/**
* Get documents for a specific Wohneinheit
*/
protected function getWohneinheitDocumentsAction()
{
if (empty($this->request->wohneinheitId)) self::sendError("Wohneinheit-ID fehlt.");
$wohneinheitId = intval($this->request->wohneinheitId);
$db = FronkDB::singleton(ADDRESSDB_DBHOST, ADDRESSDB_DBUSER, ADDRESSDB_DBPASS, ADDRESSDB_DBNAME);
$sql = "SELECT * FROM WohneinheitDocumentation WHERE wohneinheit_id = " . $db->escape($wohneinheitId) . " ORDER BY `create` ASC";
$result = $db->query($sql);
$docs = $result ? $result->fetch_all(MYSQLI_ASSOC) : [];
$responseDocs = [];
foreach ($docs as $doc) {
$file = new File($doc['fileId']);
$responseDocs[] = [
'id' => $doc['id'],
'fileId' => $doc['fileId'],
'fileName' => $file->orig_filename ?? $file->filename,
'description' => $doc['description'],
'documentType' => $doc['documentType'],
'userName' => UserModel::getOne($doc['createBy'])->name ?? 'Unbekannt',
'mimetype' => $file->mimetype ?? 'application/octet-stream',
'create' => $doc['create']
];
}
self::returnJson(['docs' => $responseDocs]);
}
/**
* Upload document for a specific Wohneinheit
*/
protected function uploadWohneinheitDocumentAction()
{
if (empty($_FILES['file']) || empty($_POST['wohneinheitId'])) {
self::sendError("Datei und Wohneinheit-ID sind erforderlich.");
}
$wohneinheitId = intval($_POST['wohneinheitId']);
$documentType = $_POST['documentType'] ?? 'photo';
$description = $_POST['description'] ?? null;
// Upload file using mfUpload handleFormUpload for proper handling
try {
$upload = mfUpload::handleFormUpload("file", false, "/WorkorderMph/Wohneinheit");
$file = $upload; // handleFormUpload returns the File object
} catch (Exception $e) {
self::sendError("Datei-Upload fehlgeschlagen: " . $e->getMessage());
return;
}
// Insert into WohneinheitDocumentation table in addressdb
$db = FronkDB::singleton(ADDRESSDB_DBHOST, ADDRESSDB_DBUSER, ADDRESSDB_DBPASS, ADDRESSDB_DBNAME);
$escapedWohneinheitId = $db->escape($wohneinheitId);
$escapedFileId = $db->escape($file->id);
$escapedDescription = $description ? "'" . $db->escape($description) . "'" : "NULL";
$escapedDocumentType = "'" . $db->escape($documentType) . "'";
$escapedCreateBy = $db->escape($this->user->id);
$escapedCreate = time();
$sql = "INSERT INTO WohneinheitDocumentation (wohneinheit_id, fileId, description, documentType, `create`, createBy)
VALUES ($escapedWohneinheitId, $escapedFileId, $escapedDescription, $escapedDocumentType, $escapedCreate, $escapedCreateBy)";
$db->query($sql);
self::returnJson(['success' => true, 'message' => 'Dokument erfolgreich hochgeladen.', 'fileId' => $file->id]);
}
/**
* Delete document for a specific Wohneinheit
*/
protected function deleteWohneinheitDocumentAction()
{
if (empty($this->postData['documentationId'])) self::sendError("Dokumentations-ID fehlt.");
$documentationId = intval($this->postData['documentationId']);
$db = FronkDB::singleton(ADDRESSDB_DBHOST, ADDRESSDB_DBUSER, ADDRESSDB_DBPASS, ADDRESSDB_DBNAME);
$escapedId = $db->escape($documentationId);
$sql = "DELETE FROM WohneinheitDocumentation WHERE id = $escapedId";
$db->query($sql);
self::returnJson(['success' => true, 'message' => 'Dokument gelöscht.']);
}
/**
* 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' => 'Leitungsrecht',
'btb' => 'Bautechnische Begehung',
'fttxLocationSupplied' => 'FTTx Location mit Leerrohr versorgt',
'conduitToHuepLaid' => 'Leerrohr bis HÜP/HAK verlegt',
'huepMounted' => 'HÜP/HAK montiert',
'dropCableAvailable' => 'Dropkabel vorhanden',
'spliceCompleted' => 'Spleiß abgeschlossen'
];
$updateHausnummerStatus = false;
foreach ($checkboxFields as $field => $fieldLabel) {
if (array_key_exists($field, $post)) {
$oldValue = $workorder->$field;
$newValue = $post[$field] ? 1 : 0;
if ($oldValue !== $newValue) {
$workorder->$field = $newValue;
// Only log changes where newValue is 'ja' or oldValue was 'ja' (changing from yes to no)
if ($newValue === 1 || $oldValue === 1) {
$changes[] = "$fieldLabel: " . ($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
}