Workorder mph/improve
This commit is contained in:
@@ -144,6 +144,8 @@
|
|||||||
<!-- add a new line Arbeitsaufträge for RMLCompany, add a new line Arbeitsaufträge-Management for RMLAdmin -->
|
<!-- add a new line Arbeitsaufträge for RMLCompany, add a new line Arbeitsaufträge-Management for RMLAdmin -->
|
||||||
<?php if($me->can("RMLCompany")): ?><li><a href="<?=self::getUrl("WorkorderCompany")?>"><i class="far fa-fw fa-clipboard-question text-info"></i> Arbeitsaufträge</a></li><?php endif; ?>
|
<?php if($me->can("RMLCompany")): ?><li><a href="<?=self::getUrl("WorkorderCompany")?>"><i class="far fa-fw fa-clipboard-question text-info"></i> Arbeitsaufträge</a></li><?php endif; ?>
|
||||||
<?php if($me->can("RMLAdmin")): ?><li><a href="<?=self::getUrl("WorkorderAdmin")?>"><i class="far fa-fw fa-clipboard-question text-info"></i> Arbeitsaufträge-Management</a></li><?php endif; ?>
|
<?php if($me->can("RMLAdmin")): ?><li><a href="<?=self::getUrl("WorkorderAdmin")?>"><i class="far fa-fw fa-clipboard-question text-info"></i> Arbeitsaufträge-Management</a></li><?php endif; ?>
|
||||||
|
<?php if($me->can("WorkorderMph")): ?><li><a href="<?=self::getUrl("WorkorderMphCompany")?>"><i class="far fa-fw fa-buildings text-info"></i> MPH Arbeitsaufträge</a></li><?php endif; ?>
|
||||||
|
<?php if($me->can("WorkorderMphAdmin")): ?><li><a href="<?=self::getUrl("WorkorderMphAdmin")?>"><i class="far fa-fw fa-buildings text-info"></i> MPH Arbeitsaufträge Verwaltung</a></li><?php endif; ?>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|||||||
@@ -17,14 +17,10 @@ class RadiusController extends mfBaseController {
|
|||||||
protected function indexAction() {
|
protected function indexAction() {
|
||||||
$this->layout()->set('additionalJS', ["plugins/chart.js/chart.4.4.6.js", "plugins/chart.js/chartjs-adapter-moment.min.js"]);
|
$this->layout()->set('additionalJS', ["plugins/chart.js/chart.4.4.6.js", "plugins/chart.js/chartjs-adapter-moment.min.js"]);
|
||||||
|
|
||||||
$allowedAcsUserIds = [9, 13, 25, 65, 135, 145, 178];
|
|
||||||
$acsEnabled = in_array($this->me->id, $allowedAcsUserIds);
|
|
||||||
|
|
||||||
Helper::renderVue3($this, $this->mod, "Radius", [
|
Helper::renderVue3($this, $this->mod, "Radius", [
|
||||||
'CAN_BILLING' => $this->me->can("Billing"),
|
'CAN_BILLING' => $this->me->can("Billing"),
|
||||||
'HIDE_PAGE_TITLE' => true,
|
'HIDE_PAGE_TITLE' => true,
|
||||||
'USER_ID' => $this->me->id,
|
'USER_ID' => $this->me->id,
|
||||||
'ACS_ENABLED' => $acsEnabled
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,13 +282,14 @@ class RadiusController extends mfBaseController {
|
|||||||
try {
|
try {
|
||||||
$input = json_decode(file_get_contents('php://input'), true);
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
$deviceId = $input['deviceId'] ?? null;
|
$deviceId = $input['deviceId'] ?? null;
|
||||||
$this->log->debug("genieacsRemoteAccessAction", ['deviceId' => $deviceId]);
|
$forceRecreate = $input['forceRecreate'] ?? false;
|
||||||
|
$this->log->debug("genieacsRemoteAccessAction", ['deviceId' => $deviceId, 'forceRecreate' => $forceRecreate]);
|
||||||
|
|
||||||
if (!$deviceId) self::sendError("Device ID is required");
|
if (!$deviceId) self::sendError("Device ID is required");
|
||||||
|
|
||||||
$acs = $this->getGenieACS();
|
$acs = $this->getGenieACS();
|
||||||
$result = $acs->createRemoteUser($deviceId);
|
$result = $acs->createRemoteUser($deviceId, $forceRecreate);
|
||||||
|
|
||||||
if ($result) {
|
if ($result) {
|
||||||
self::returnJson(['success' => true] + $result);
|
self::returnJson(['success' => true] + $result);
|
||||||
} else {
|
} else {
|
||||||
@@ -304,19 +301,71 @@ class RadiusController extends mfBaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function genieacsEventLogAction() {
|
||||||
|
try {
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$deviceId = $input['deviceId'] ?? null;
|
||||||
|
$this->log->debug("genieacsEventLogAction", ['deviceId' => $deviceId]);
|
||||||
|
|
||||||
|
if (!$deviceId) self::sendError("Device ID is required");
|
||||||
|
|
||||||
|
$acs = $this->getGenieACS();
|
||||||
|
$creds = $acs->createRemoteUser($deviceId);
|
||||||
|
|
||||||
|
if (!$creds) self::sendError("Could not obtain credentials for FritzBox");
|
||||||
|
|
||||||
|
$url = "http://acs.xinon.at:5000/read-fritz-eventlog";
|
||||||
|
$apiKey = "2H9zWrgxPEJL9MZ1yTGtWh16cPCu0AsQ";
|
||||||
|
|
||||||
|
$data = json_encode([
|
||||||
|
'fritz_ip' => $creds['ip'],
|
||||||
|
'fritz_port' => "9090",
|
||||||
|
'fritz_user' => $creds['username'],
|
||||||
|
'fritz_pass' => $creds['password']
|
||||||
|
]);
|
||||||
|
|
||||||
|
$opts = [
|
||||||
|
"http" => [
|
||||||
|
"method" => "POST",
|
||||||
|
"header" => "Content-Type: application/json\r\n" .
|
||||||
|
"X-API-Key: " . $apiKey . "\r\n" .
|
||||||
|
"Content-Length: " . strlen($data) . "\r\n",
|
||||||
|
"content" => $data,
|
||||||
|
"timeout" => 60
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$context = stream_context_create($opts);
|
||||||
|
$response = file_get_contents($url, false, $context);
|
||||||
|
|
||||||
|
if ($response) {
|
||||||
|
$json = json_decode($response, true);
|
||||||
|
if ($json && isset($json['data'])) {
|
||||||
|
self::returnJson(['success' => true, 'events' => $json['data']]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self::sendError("Failed to fetch event log");
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->log->debug("Event Log Error", ['error' => $e->getMessage()]);
|
||||||
|
self::sendError("Error: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected function genieacsNetworkStructureAction() {
|
protected function genieacsNetworkStructureAction() {
|
||||||
try {
|
try {
|
||||||
$input = json_decode(file_get_contents('php://input'), true);
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
$deviceId = $input['deviceId'] ?? null;
|
$deviceId = $input['deviceId'] ?? null;
|
||||||
$this->log->debug("genieacsNetworkStructureAction", ['deviceId' => $deviceId]);
|
$this->log->debug("genieacsNetworkStructureAction", ['deviceId' => $deviceId]);
|
||||||
|
|
||||||
if (!$deviceId) self::sendError("Device ID is required");
|
if (!$deviceId) self::sendError("Device ID is required");
|
||||||
|
|
||||||
$acs = $this->getGenieACS();
|
$acs = $this->getGenieACS();
|
||||||
$creds = $acs->createRemoteUser($deviceId);
|
$creds = $acs->createRemoteUser($deviceId);
|
||||||
|
|
||||||
if (!$creds) self::sendError("Could not obtain credentials for FritzBox");
|
if (!$creds) self::sendError("Could not obtain credentials for FritzBox");
|
||||||
|
|
||||||
$url = "http://acs.xinon.at:5000/read-fritz";
|
$url = "http://acs.xinon.at:5000/read-fritz";
|
||||||
$apiKey = "2H9zWrgxPEJL9MZ1yTGtWh16cPCu0AsQ";
|
$apiKey = "2H9zWrgxPEJL9MZ1yTGtWh16cPCu0AsQ";
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ class WorkorderMphAdminController extends WorkorderMphBaseController
|
|||||||
|
|
||||||
protected array $columns = [
|
protected array $columns = [
|
||||||
['key' => 'id', 'text' => 'Auftrags-Nr.', 'table' => ['sortable' => true, 'filter' => 'numberRange']],
|
['key' => 'id', 'text' => 'Auftrags-Nr.', 'table' => ['sortable' => true, 'filter' => 'numberRange']],
|
||||||
|
['key' => 'netOwnerId', 'text' => 'Netzeigentümer', 'modal' => false, 'table' => ['filter' => 'select', 'sortable' => false], 'required' => false],
|
||||||
['key' => 'hausnummerInfo', 'text' => 'Adresse', 'modal' => false, 'table' => ['filter' => 'search', 'sortable' => false]],
|
['key' => 'hausnummerInfo', 'text' => 'Adresse', 'modal' => false, 'table' => ['filter' => 'search', 'sortable' => false]],
|
||||||
['key' => 'netzgebietName', 'text' => 'Netzgebiet', 'modal' => false, 'table' => ['filter' => 'search', 'sortable' => true]],
|
['key' => 'netzgebietName', 'text' => 'Netzgebiet', 'modal' => false, 'table' => ['filter' => 'select', 'sortable' => false]],
|
||||||
['key' => 'companyName', 'text' => 'Firma', 'modal' => false, 'table' => ['filter' => 'search', 'sortable' => true]],
|
['key' => 'rimoFcpName', 'text' => 'FCP', 'modal' => false, 'table' => ['filter' => 'search', 'sortable' => false]],
|
||||||
['key' => 'wohneinheitCount', 'text' => 'WE', 'modal' => false, 'table' => ['sortable' => false]],
|
['key' => 'companyName', 'text' => 'Firma', 'modal' => false, 'table' => ['filter' => 'search', 'sortable' => false]],
|
||||||
['key' => 'additionalInfo', 'text' => 'Notiz', 'modal' => false, 'table' => ['sortable' => true]],
|
['key' => 'wohneinheitCount', 'text' => 'WE', 'modal' => false, 'table' => ['sortable' => true, 'filter' => 'numberRange']],
|
||||||
|
['key' => 'additionalInfo', 'text' => 'Notiz', 'modal' => false, 'table' => ['sortable' => false]],
|
||||||
['key' => 'deadlineDate', 'text' => 'Deadline', 'modal' => false, 'table' => ['filter' => 'date', 'sortable' => true]],
|
['key' => 'deadlineDate', 'text' => 'Deadline', 'modal' => false, 'table' => ['filter' => 'date', 'sortable' => true]],
|
||||||
['key' => 'appointmentDate', 'text' => 'Termin', 'modal' => false, 'table' => ['filter' => 'date', 'sortable' => true]],
|
['key' => 'appointmentDate', 'text' => 'Termin', 'modal' => false, 'table' => ['filter' => 'date', 'sortable' => true]],
|
||||||
];
|
];
|
||||||
@@ -21,11 +23,49 @@ class WorkorderMphAdminController extends WorkorderMphBaseController
|
|||||||
{
|
{
|
||||||
$hausnummerInfoColIdx = array_search('hausnummerInfo', array_column($this->columns, 'key'));
|
$hausnummerInfoColIdx = array_search('hausnummerInfo', array_column($this->columns, 'key'));
|
||||||
array_splice($this->columns, $hausnummerInfoColIdx + 1, 0, [$this->statusColumn]);
|
array_splice($this->columns, $hausnummerInfoColIdx + 1, 0, [$this->statusColumn]);
|
||||||
|
|
||||||
|
// Handle netOwnerId column - only visible for admins
|
||||||
|
$netOwnerColIdx = array_search('netOwnerId', array_column($this->columns, 'key'));
|
||||||
|
if ($netOwnerColIdx !== false) {
|
||||||
|
if ($this->user->isAdmin()) {
|
||||||
|
$netOwners = Helper::getMphNetworkOwners();
|
||||||
|
$this->columns[$netOwnerColIdx]['table']['filterOptions'] = array_map(fn($o) => ['value' => $o->id, 'text' => $o->company], $netOwners);
|
||||||
|
} else {
|
||||||
|
$this->columns[$netOwnerColIdx]['table'] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate netzgebiet filter options
|
||||||
|
$netzgebietColIdx = array_search('netzgebietName', array_column($this->columns, 'key'));
|
||||||
|
if ($netzgebietColIdx !== false) {
|
||||||
|
$db = FronkDB::singleton(ADDRESSDB_DBHOST, ADDRESSDB_DBUSER, ADDRESSDB_DBPASS, ADDRESSDB_DBNAME);
|
||||||
|
|
||||||
|
// Apply network ownership filtering
|
||||||
|
$netzgebietFilter = "";
|
||||||
|
if (!$this->user->isAdmin()) {
|
||||||
|
$allowedNetzgebietIds = Helper::getADBNetworksFromUser($this->user);
|
||||||
|
if (!empty($allowedNetzgebietIds)) {
|
||||||
|
$escapedIds = array_map(fn($id) => $db->escape($id), $allowedNetzgebietIds);
|
||||||
|
$netzgebietFilter = " AND ng.id IN (" . implode(',', $escapedIds) . ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$fronkDbName = FRONKDB_DBNAME;
|
||||||
|
$sql = "SELECT DISTINCT ng.id, ng.name FROM Netzgebiet ng
|
||||||
|
INNER JOIN Hausnummer hn ON ng.id = hn.netzgebiet_id
|
||||||
|
INNER JOIN `$fronkDbName`.`WorkorderMph` wm ON wm.hausnummerId = hn.id
|
||||||
|
WHERE ng.name IS NOT NULL AND ng.name != ''
|
||||||
|
$netzgebietFilter
|
||||||
|
ORDER BY ng.name ASC";
|
||||||
|
$result = $db->query($sql);
|
||||||
|
$netzgebiete = $result ? $result->fetch_all(MYSQLI_ASSOC) : [];
|
||||||
|
$this->columns[$netzgebietColIdx]['table']['filterOptions'] = array_map(fn($ng) => ['value' => $ng['id'], 'text' => $ng['name']], $netzgebiete);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function indexAction()
|
public function indexAction()
|
||||||
{
|
{
|
||||||
$this->createWorkordersFromHausnummer();
|
// Note: Workorder creation is now handled by cronjob script: scripts/workorder-mph-create-from-hausnummer.php
|
||||||
parent::indexAction();
|
parent::indexAction();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +81,18 @@ class WorkorderMphAdminController extends WorkorderMphBaseController
|
|||||||
|
|
||||||
$whereClauses = "WHERE 1=1";
|
$whereClauses = "WHERE 1=1";
|
||||||
|
|
||||||
|
// Apply network ownership filtering (similar to WorkorderAdmin)
|
||||||
|
if (!$this->user->isAdmin()) {
|
||||||
|
$allowedNetzgebietIds = Helper::getADBNetworksFromUser($this->user);
|
||||||
|
if (!empty($allowedNetzgebietIds)) {
|
||||||
|
$escapedIds = array_map(fn($id) => $db->escape($id), $allowedNetzgebietIds);
|
||||||
|
$whereClauses .= " AND hn.netzgebiet_id IN (" . implode(',', $escapedIds) . ")";
|
||||||
|
} else {
|
||||||
|
// User has no networks assigned, show no results
|
||||||
|
$whereClauses .= " AND 1=0";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (empty($filters['status'])) {
|
if (empty($filters['status'])) {
|
||||||
$whereClauses .= " AND w.status NOT IN ('completed', 'cancelled', 'archived')";
|
$whereClauses .= " AND w.status NOT IN ('completed', 'cancelled', 'archived')";
|
||||||
} else {
|
} else {
|
||||||
@@ -48,12 +100,15 @@ class WorkorderMphAdminController extends WorkorderMphBaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($filters['id'])) $whereClauses .= Helper::generateFilterCondition($filters['id'], 'w.id', true);
|
if (!empty($filters['id'])) $whereClauses .= Helper::generateFilterCondition($filters['id'], 'w.id', true);
|
||||||
|
if (!empty($filters['netOwnerId'])) $whereClauses .= Helper::generateFilterCondition($filters['netOwnerId'], 'n.owner_id');
|
||||||
if (!empty($filters['hausnummerInfo'])) {
|
if (!empty($filters['hausnummerInfo'])) {
|
||||||
$searchColumns = "str.name|hn.hausnummer|hn.stiege|plz.plz|ort.name|w.additionalInfo";
|
$searchColumns = "str.name|hn.hausnummer|hn.stiege|plz.plz|ort.name|w.additionalInfo";
|
||||||
$whereClauses .= Helper::generateFilterCondition($filters['hausnummerInfo'], $searchColumns);
|
$whereClauses .= Helper::generateFilterCondition($filters['hausnummerInfo'], $searchColumns);
|
||||||
}
|
}
|
||||||
if (!empty($filters['netzgebietName'])) $whereClauses .= Helper::generateFilterCondition($filters['netzgebietName'], 'ng.name');
|
if (!empty($filters['netzgebietName'])) $whereClauses .= Helper::generateFilterCondition($filters['netzgebietName'], 'ng.id');
|
||||||
|
if (!empty($filters['rimoFcpName'])) $whereClauses .= Helper::generateFilterCondition($filters['rimoFcpName'], 'hn.rimo_fcp_name');
|
||||||
if (!empty($filters['companyName'])) $whereClauses .= Helper::generateFilterCondition($filters['companyName'], 'c.name');
|
if (!empty($filters['companyName'])) $whereClauses .= Helper::generateFilterCondition($filters['companyName'], 'c.name');
|
||||||
|
if (!empty($filters['wohneinheitCount'])) $whereClauses .= Helper::generateFilterCondition($filters['wohneinheitCount'], '(SELECT COUNT(*) FROM `' . $addressDbName . '`.`Wohneinheit` we WHERE we.hausnummer_id = hn.id)', true);
|
||||||
if (!empty($filters['deadlineDate'])) $whereClauses .= Helper::generateFilterCondition($filters['deadlineDate'], 'w.deadlineDate');
|
if (!empty($filters['deadlineDate'])) $whereClauses .= Helper::generateFilterCondition($filters['deadlineDate'], 'w.deadlineDate');
|
||||||
if (!empty($filters['additionalInfo'])) $whereClauses .= Helper::generateFilterCondition($filters['additionalInfo'], 'w.additionalInfo');
|
if (!empty($filters['additionalInfo'])) $whereClauses .= Helper::generateFilterCondition($filters['additionalInfo'], 'w.additionalInfo');
|
||||||
|
|
||||||
@@ -63,7 +118,9 @@ class WorkorderMphAdminController extends WorkorderMphBaseController
|
|||||||
IFNULL(c.name, 'Nicht zugewiesen') as companyName,
|
IFNULL(c.name, 'Nicht zugewiesen') as companyName,
|
||||||
CONCAT_WS(' ', str.name, hn.hausnummer, hn.stiege) as hausnummerInfo,
|
CONCAT_WS(' ', str.name, hn.hausnummer, hn.stiege) as hausnummerInfo,
|
||||||
str.name as street, hn.hausnummer, hn.stiege, plz.plz, ort.name as city,
|
str.name as street, hn.hausnummer, hn.stiege, plz.plz, ort.name as city,
|
||||||
IFNULL(ng.name, '-') as netzgebietName,
|
ng.id as netzgebietName,
|
||||||
|
n.owner_id as netOwnerId,
|
||||||
|
hn.rimo_fcp_name as rimoFcpName,
|
||||||
(SELECT COUNT(*) FROM `$addressDbName`.`Wohneinheit` we WHERE we.hausnummer_id = hn.id) as wohneinheitCount
|
(SELECT COUNT(*) FROM `$addressDbName`.`Wohneinheit` we WHERE we.hausnummer_id = hn.id) as wohneinheitCount
|
||||||
FROM `$fronkDbName`.`WorkorderMph` w
|
FROM `$fronkDbName`.`WorkorderMph` w
|
||||||
LEFT JOIN `$fronkDbName`.`WorkorderCompany` c ON w.companyId = c.id
|
LEFT JOIN `$fronkDbName`.`WorkorderCompany` c ON w.companyId = c.id
|
||||||
@@ -72,12 +129,13 @@ class WorkorderMphAdminController extends WorkorderMphBaseController
|
|||||||
LEFT JOIN `$addressDbName`.`Plz` plz ON hn.plz_id = plz.id
|
LEFT JOIN `$addressDbName`.`Plz` plz ON hn.plz_id = plz.id
|
||||||
LEFT JOIN `$addressDbName`.`Ortschaft` ort ON hn.ortschaft_id = ort.id
|
LEFT JOIN `$addressDbName`.`Ortschaft` ort ON hn.ortschaft_id = ort.id
|
||||||
LEFT JOIN `$addressDbName`.`Netzgebiet` ng ON hn.netzgebiet_id = ng.id
|
LEFT JOIN `$addressDbName`.`Netzgebiet` ng ON hn.netzgebiet_id = ng.id
|
||||||
|
LEFT JOIN `$fronkDbName`.`Network` n ON n.adb_netzgebiet_id = ng.id
|
||||||
$whereClauses
|
$whereClauses
|
||||||
";
|
";
|
||||||
|
|
||||||
$orderBy = "";
|
$orderBy = "";
|
||||||
if (!empty($order['key'])) {
|
if (!empty($order['key'])) {
|
||||||
$sortableColumns = ['id', 'status', 'deadlineDate', 'companyName', 'additionalInfo', 'appointmentDate', 'netzgebietName'];
|
$sortableColumns = ['id', 'status', 'deadlineDate', 'appointmentDate', 'wohneinheitCount'];
|
||||||
if (in_array($order['key'], $sortableColumns)) {
|
if (in_array($order['key'], $sortableColumns)) {
|
||||||
$sortOrder = (strtoupper($order['order']) === 'DESC') ? 'DESC' : 'ASC';
|
$sortOrder = (strtoupper($order['order']) === 'DESC') ? 'DESC' : 'ASC';
|
||||||
$orderBy = " ORDER BY " . $db->escape($order['key']) . " " . $sortOrder;
|
$orderBy = " ORDER BY " . $db->escape($order['key']) . " " . $sortOrder;
|
||||||
@@ -95,8 +153,9 @@ class WorkorderMphAdminController extends WorkorderMphBaseController
|
|||||||
LEFT JOIN `$addressDbName`.`Plz` plz ON hn.plz_id = plz.id
|
LEFT JOIN `$addressDbName`.`Plz` plz ON hn.plz_id = plz.id
|
||||||
LEFT JOIN `$addressDbName`.`Ortschaft` ort ON hn.ortschaft_id = ort.id
|
LEFT JOIN `$addressDbName`.`Ortschaft` ort ON hn.ortschaft_id = ort.id
|
||||||
LEFT JOIN `$addressDbName`.`Netzgebiet` ng ON hn.netzgebiet_id = ng.id
|
LEFT JOIN `$addressDbName`.`Netzgebiet` ng ON hn.netzgebiet_id = ng.id
|
||||||
|
LEFT JOIN `$fronkDbName`.`Network` n ON n.adb_netzgebiet_id = ng.id
|
||||||
$whereClauses";
|
$whereClauses";
|
||||||
$totalCount = $db->query($countSql)->fetch_assoc()['count'];
|
$totalCount = (int)$db->query($countSql)->fetch_assoc()['count'];
|
||||||
|
|
||||||
// Add pagination
|
// Add pagination
|
||||||
if ($pagination['per_page'] !== null) {
|
if ($pagination['per_page'] !== null) {
|
||||||
@@ -109,10 +168,10 @@ class WorkorderMphAdminController extends WorkorderMphBaseController
|
|||||||
self::returnJson([
|
self::returnJson([
|
||||||
'rows' => $rows,
|
'rows' => $rows,
|
||||||
'pagination' => [
|
'pagination' => [
|
||||||
'page' => $pagination['page'],
|
'page' => (int)$pagination['page'],
|
||||||
'per_page' => $pagination['per_page'],
|
'per_page' => (int)$pagination['per_page'],
|
||||||
'total_rows' => $totalCount,
|
'total_rows' => $totalCount,
|
||||||
'total_pages' => ceil($totalCount / $pagination['per_page']),
|
'total_pages' => (int)ceil($totalCount / $pagination['per_page']),
|
||||||
'filtered_available' => $totalCount
|
'filtered_available' => $totalCount
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
class WorkorderMphBaseController extends TTCrud
|
class WorkorderMphBaseController extends TTCrud
|
||||||
{
|
{
|
||||||
protected array $statusColumn = [
|
protected array $statusColumn = [
|
||||||
'key' => 'status', 'text' => 'Status', 'modal' => false, 'table' => ['filter' => 'iconSelect', 'filterOptions' => [
|
'key' => 'status', 'text' => 'Status', 'modal' => false, 'table' => ['filter' => 'iconSelect', 'sortable' => false, 'filterOptions' => [
|
||||||
['value' => 'new', 'text' => 'Neu', 'icon' => 'fas fa-star text-primary'],
|
['value' => 'new', 'text' => 'Neu', 'icon' => 'fas fa-star text-primary'],
|
||||||
['value' => 'assigned', 'text' => 'Zugewiesen', 'icon' => 'fas fa-user-check text-info'],
|
['value' => 'assigned', 'text' => 'Zugewiesen', 'icon' => 'fas fa-user-check text-info'],
|
||||||
['value' => 'scheduled', 'text' => 'Geplant', 'icon' => 'fas fa-calendar-check text-warning'],
|
['value' => 'scheduled', 'text' => 'Geplant', 'icon' => 'fas fa-calendar-check text-warning'],
|
||||||
@@ -523,7 +523,10 @@ class WorkorderMphBaseController extends TTCrud
|
|||||||
$newValue = $post[$field] ? 1 : 0;
|
$newValue = $post[$field] ? 1 : 0;
|
||||||
if ($oldValue !== $newValue) {
|
if ($oldValue !== $newValue) {
|
||||||
$workorder->$field = $newValue;
|
$workorder->$field = $newValue;
|
||||||
$changes[] = "$fieldLabel: " . ($newValue ? 'ja' : 'nein');
|
// 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
|
// Check for FTTx Location mit Leerrohr versorgt
|
||||||
if ($field === 'fttxLocationSupplied' && $newValue === 1) {
|
if ($field === 'fttxLocationSupplied' && $newValue === 1) {
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ class WorkorderMphCompanyController extends WorkorderMphBaseController
|
|||||||
protected array $permissionCheck = ['RMLCompany'];
|
protected array $permissionCheck = ['RMLCompany'];
|
||||||
|
|
||||||
protected array $columns = [
|
protected array $columns = [
|
||||||
['key' => 'id', 'text' => 'Auftrags-Nr.', 'table' => ['sortable' => true]],
|
['key' => 'id', 'text' => 'Auftrags-Nr.', 'table' => ['sortable' => true, 'filter' => 'numberRange']],
|
||||||
['key' => 'hausnummerInfo', 'text' => 'Adresse', 'modal' => false, 'table' => ['sortable' => false]],
|
['key' => 'hausnummerInfo', 'text' => 'Adresse', 'modal' => false, 'table' => ['filter' => 'search', 'sortable' => false]],
|
||||||
['key' => 'wohneinheitCount', 'text' => 'WE', 'modal' => false, 'table' => ['sortable' => false]],
|
['key' => 'netzgebietName', 'text' => 'Netzgebiet', 'modal' => false, 'table' => ['filter' => 'select', 'sortable' => false]],
|
||||||
['key' => 'additionalInfo', 'text' => 'Notiz', 'modal' => false, 'table' => ['sortable' => true]],
|
['key' => 'rimoFcpName', 'text' => 'FCP', 'modal' => false, 'table' => ['filter' => 'search', 'sortable' => false]],
|
||||||
|
['key' => 'additionalInfo', 'text' => 'Notiz', 'modal' => false, 'table' => ['sortable' => false]],
|
||||||
['key' => 'deadlineDate', 'text' => 'Deadline', 'modal' => false, 'table' => ['filter' => 'date', 'sortable' => true]],
|
['key' => 'deadlineDate', 'text' => 'Deadline', 'modal' => false, 'table' => ['filter' => 'date', 'sortable' => true]],
|
||||||
['key' => 'appointmentDate', 'text' => 'Termin', 'modal' => false, 'table' => ['filter' => 'date', 'sortable' => true]],
|
['key' => 'appointmentDate', 'text' => 'Termin', 'modal' => false, 'table' => ['filter' => 'date', 'sortable' => true]],
|
||||||
];
|
];
|
||||||
@@ -23,6 +24,22 @@ class WorkorderMphCompanyController extends WorkorderMphBaseController
|
|||||||
|
|
||||||
$company = WorkorderCompanyModel::getFirst(['addressId' => $this->user->address_id]);
|
$company = WorkorderCompanyModel::getFirst(['addressId' => $this->user->address_id]);
|
||||||
$this->additionalJSVariables['COMPANY_ID'] = $company ? $company->id : 0;
|
$this->additionalJSVariables['COMPANY_ID'] = $company ? $company->id : 0;
|
||||||
|
|
||||||
|
// Populate netzgebiet filter options for this company's workorders
|
||||||
|
$netzgebietColIdx = array_search('netzgebietName', array_column($this->columns, 'key'));
|
||||||
|
if ($netzgebietColIdx !== false && $company) {
|
||||||
|
$db = FronkDB::singleton(ADDRESSDB_DBHOST, ADDRESSDB_DBUSER, ADDRESSDB_DBPASS, ADDRESSDB_DBNAME);
|
||||||
|
$fronkDbName = FRONKDB_DBNAME;
|
||||||
|
|
||||||
|
$sql = "SELECT DISTINCT ng.id, ng.name FROM Netzgebiet ng
|
||||||
|
INNER JOIN Hausnummer hn ON ng.id = hn.netzgebiet_id
|
||||||
|
INNER JOIN `$fronkDbName`.`WorkorderMph` wm ON wm.hausnummerId = hn.id
|
||||||
|
WHERE ng.name IS NOT NULL AND ng.name != '' AND wm.companyId = " . intval($company->id) . "
|
||||||
|
ORDER BY ng.name ASC";
|
||||||
|
$result = $db->query($sql);
|
||||||
|
$netzgebiete = $result ? $result->fetch_all(MYSQLI_ASSOC) : [];
|
||||||
|
$this->columns[$netzgebietColIdx]['table']['filterOptions'] = array_map(fn($ng) => ['value' => $ng['id'], 'text' => $ng['name']], $netzgebiete);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getAction()
|
protected function getAction()
|
||||||
@@ -54,6 +71,8 @@ class WorkorderMphCompanyController extends WorkorderMphBaseController
|
|||||||
$searchColumns = "str.name|hn.hausnummer|hn.stiege|plz.plz|ort.name|w.additionalInfo";
|
$searchColumns = "str.name|hn.hausnummer|hn.stiege|plz.plz|ort.name|w.additionalInfo";
|
||||||
$whereClauses .= Helper::generateFilterCondition($filters['hausnummerInfo'], $searchColumns);
|
$whereClauses .= Helper::generateFilterCondition($filters['hausnummerInfo'], $searchColumns);
|
||||||
}
|
}
|
||||||
|
if (!empty($filters['netzgebietName'])) $whereClauses .= Helper::generateFilterCondition($filters['netzgebietName'], 'ng.id');
|
||||||
|
if (!empty($filters['rimoFcpName'])) $whereClauses .= Helper::generateFilterCondition($filters['rimoFcpName'], 'hn.rimo_fcp_name');
|
||||||
if (!empty($filters['deadlineDate'])) $whereClauses .= Helper::generateFilterCondition($filters['deadlineDate'], 'w.deadlineDate');
|
if (!empty($filters['deadlineDate'])) $whereClauses .= Helper::generateFilterCondition($filters['deadlineDate'], 'w.deadlineDate');
|
||||||
if (!empty($filters['appointmentDate'])) $whereClauses .= Helper::generateFilterCondition($filters['appointmentDate'], 'w.appointmentDate');
|
if (!empty($filters['appointmentDate'])) $whereClauses .= Helper::generateFilterCondition($filters['appointmentDate'], 'w.appointmentDate');
|
||||||
if (!empty($filters['additionalInfo'])) $whereClauses .= Helper::generateFilterCondition($filters['additionalInfo'], 'w.additionalInfo');
|
if (!empty($filters['additionalInfo'])) $whereClauses .= Helper::generateFilterCondition($filters['additionalInfo'], 'w.additionalInfo');
|
||||||
@@ -63,18 +82,21 @@ class WorkorderMphCompanyController extends WorkorderMphBaseController
|
|||||||
w.id, w.status, w.deadlineDate, w.appointmentDate, w.additionalInfo,
|
w.id, w.status, w.deadlineDate, w.appointmentDate, w.additionalInfo,
|
||||||
CONCAT_WS(' ', str.name, hn.hausnummer, hn.stiege) as hausnummerInfo,
|
CONCAT_WS(' ', str.name, hn.hausnummer, hn.stiege) as hausnummerInfo,
|
||||||
str.name as street, hn.hausnummer, hn.stiege, plz.plz, ort.name as city,
|
str.name as street, hn.hausnummer, hn.stiege, plz.plz, ort.name as city,
|
||||||
|
ng.id as netzgebietName,
|
||||||
|
hn.rimo_fcp_name as rimoFcpName,
|
||||||
(SELECT COUNT(*) FROM `$addressDbName`.`Wohneinheit` we WHERE we.hausnummer_id = hn.id) as wohneinheitCount
|
(SELECT COUNT(*) FROM `$addressDbName`.`Wohneinheit` we WHERE we.hausnummer_id = hn.id) as wohneinheitCount
|
||||||
FROM `$fronkDbName`.`WorkorderMph` w
|
FROM `$fronkDbName`.`WorkorderMph` w
|
||||||
LEFT JOIN `$addressDbName`.`Hausnummer` hn ON w.hausnummerId = hn.id
|
LEFT JOIN `$addressDbName`.`Hausnummer` hn ON w.hausnummerId = hn.id
|
||||||
LEFT JOIN `$addressDbName`.`Strasse` str ON hn.strasse_id = str.id
|
LEFT JOIN `$addressDbName`.`Strasse` str ON hn.strasse_id = str.id
|
||||||
LEFT JOIN `$addressDbName`.`Plz` plz ON hn.plz_id = plz.id
|
LEFT JOIN `$addressDbName`.`Plz` plz ON hn.plz_id = plz.id
|
||||||
LEFT JOIN `$addressDbName`.`Ortschaft` ort ON hn.ortschaft_id = ort.id
|
LEFT JOIN `$addressDbName`.`Ortschaft` ort ON hn.ortschaft_id = ort.id
|
||||||
|
LEFT JOIN `$addressDbName`.`Netzgebiet` ng ON hn.netzgebiet_id = ng.id
|
||||||
$whereClauses
|
$whereClauses
|
||||||
";
|
";
|
||||||
|
|
||||||
$orderBy = "";
|
$orderBy = "";
|
||||||
if (!empty($order['key'])) {
|
if (!empty($order['key'])) {
|
||||||
$sortableColumns = ['id', 'status', 'deadlineDate', 'additionalInfo', 'appointmentDate'];
|
$sortableColumns = ['id', 'status', 'deadlineDate', 'appointmentDate'];
|
||||||
if (in_array($order['key'], $sortableColumns)) {
|
if (in_array($order['key'], $sortableColumns)) {
|
||||||
$sortOrder = (strtoupper($order['order']) === 'DESC') ? 'DESC' : 'ASC';
|
$sortOrder = (strtoupper($order['order']) === 'DESC') ? 'DESC' : 'ASC';
|
||||||
$orderBy = " ORDER BY " . $db->escape($order['key']) . " " . $sortOrder;
|
$orderBy = " ORDER BY " . $db->escape($order['key']) . " " . $sortOrder;
|
||||||
@@ -90,8 +112,9 @@ class WorkorderMphCompanyController extends WorkorderMphBaseController
|
|||||||
LEFT JOIN `$addressDbName`.`Strasse` str ON hn.strasse_id = str.id
|
LEFT JOIN `$addressDbName`.`Strasse` str ON hn.strasse_id = str.id
|
||||||
LEFT JOIN `$addressDbName`.`Plz` plz ON hn.plz_id = plz.id
|
LEFT JOIN `$addressDbName`.`Plz` plz ON hn.plz_id = plz.id
|
||||||
LEFT JOIN `$addressDbName`.`Ortschaft` ort ON hn.ortschaft_id = ort.id
|
LEFT JOIN `$addressDbName`.`Ortschaft` ort ON hn.ortschaft_id = ort.id
|
||||||
|
LEFT JOIN `$addressDbName`.`Netzgebiet` ng ON hn.netzgebiet_id = ng.id
|
||||||
$whereClauses";
|
$whereClauses";
|
||||||
$totalCount = $db->query($countSql)->fetch_assoc()['count'];
|
$totalCount = (int)$db->query($countSql)->fetch_assoc()['count'];
|
||||||
|
|
||||||
// Add pagination
|
// Add pagination
|
||||||
if ($pagination['per_page'] !== null) {
|
if ($pagination['per_page'] !== null) {
|
||||||
@@ -104,10 +127,10 @@ class WorkorderMphCompanyController extends WorkorderMphBaseController
|
|||||||
self::returnJson([
|
self::returnJson([
|
||||||
'rows' => $rows,
|
'rows' => $rows,
|
||||||
'pagination' => [
|
'pagination' => [
|
||||||
'page' => $pagination['page'],
|
'page' => (int)$pagination['page'],
|
||||||
'per_page' => $pagination['per_page'],
|
'per_page' => (int)$pagination['per_page'],
|
||||||
'total_rows' => $totalCount,
|
'total_rows' => $totalCount,
|
||||||
'total_pages' => ceil($totalCount / $pagination['per_page']),
|
'total_pages' => (int)ceil($totalCount / $pagination['per_page']),
|
||||||
'filtered_available' => $totalCount
|
'filtered_available' => $totalCount
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
@@ -190,14 +213,6 @@ class WorkorderMphCompanyController extends WorkorderMphBaseController
|
|||||||
$workorder = WorkorderMphModel::get($this->postData['workorderId']);
|
$workorder = WorkorderMphModel::get($this->postData['workorderId']);
|
||||||
if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
|
if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
|
||||||
|
|
||||||
// Validate that all required Wohneinheiten have notes
|
|
||||||
$wohneinheiten = WorkorderMphWohneinheitModel::getAll(['workorderMphId' => $workorder->id]);
|
|
||||||
foreach ($wohneinheiten as $we) {
|
|
||||||
if (empty($we->note)) {
|
|
||||||
self::sendError("Bitte fügen Sie für jede Wohneinheit eine Notiz hinzu, bevor Sie den Auftrag abschließen.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$oldStatus = $workorder->status;
|
$oldStatus = $workorder->status;
|
||||||
$workorder->status = 'documented';
|
$workorder->status = 'documented';
|
||||||
WorkorderMphModel::update((array)$workorder);
|
WorkorderMphModel::update((array)$workorder);
|
||||||
@@ -253,4 +268,34 @@ class WorkorderMphCompanyController extends WorkorderMphBaseController
|
|||||||
WorkorderMphDocumentationModel::delete($doc->id);
|
WorkorderMphDocumentationModel::delete($doc->id);
|
||||||
self::returnJson(['success' => true, 'message' => 'Dokumentation gelöscht.']);
|
self::returnJson(['success' => true, 'message' => 'Dokumentation gelöscht.']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function updateAdditionalInfoAction()
|
||||||
|
{
|
||||||
|
if (empty($this->postData['workorderMphId'])) self::sendError("Arbeitsauftrags-ID fehlt.");
|
||||||
|
|
||||||
|
$workorder = WorkorderMphModel::get($this->postData['workorderMphId']);
|
||||||
|
if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
|
||||||
|
|
||||||
|
// Verify company access
|
||||||
|
$company = WorkorderCompanyModel::getFirst(['addressId' => $this->user->address_id]);
|
||||||
|
if (!$company || $workorder->companyId != $company->id) {
|
||||||
|
self::sendError("Keine Berechtigung für diesen Arbeitsauftrag.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$oldInfo = $workorder->additionalInfo;
|
||||||
|
$newInfo = $this->postData['additionalInfo'] ?? '';
|
||||||
|
$workorder->additionalInfo = $newInfo;
|
||||||
|
WorkorderMphModel::update((array)$workorder);
|
||||||
|
|
||||||
|
if ($oldInfo !== $newInfo) {
|
||||||
|
WorkorderMphJournalModel::create([
|
||||||
|
'workorderMphId' => $workorder->id,
|
||||||
|
'text' => "Notiz geändert: " . ($newInfo ?: '(leer)'),
|
||||||
|
'create' => time(),
|
||||||
|
'createBy' => $this->user->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
self::returnJson(['success' => true, 'message' => 'Notiz aktualisiert.', 'newInfo' => $newInfo]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ class WorkorderTenantConfigModel extends TTCrudBaseModel {
|
|||||||
public int $civilEngineeringDocsRequired;
|
public int $civilEngineeringDocsRequired;
|
||||||
public int $requireCableLength;
|
public int $requireCableLength;
|
||||||
public int $requireCableType;
|
public int $requireCableType;
|
||||||
|
public int $enableWorkorder;
|
||||||
|
public int $enableWorkorderMph;
|
||||||
public int $create;
|
public int $create;
|
||||||
public int $createBy;
|
public int $createBy;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class AddWorkorderMphPermissions extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
if($this->getEnvironment() == "thetool") {
|
||||||
|
$table = $this->table("WorkerPermission");
|
||||||
|
$table->addColumn("canWorkorderMphAdmin", "enum", ["values" => 'false,true', "default" => "false", "after" => "canRMLAdmin"]);
|
||||||
|
$table->addColumn("canWorkorderMph", "enum", ["values" => 'false,true', "default" => "false", "after" => "canWorkorderMphAdmin"]);
|
||||||
|
$table->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->getEnvironment() == "addressdb") {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
if($this->getEnvironment() == "thetool") {
|
||||||
|
$this->table("WorkerPermission")->removeColumn("canWorkorderMphAdmin")->save();
|
||||||
|
$this->table("WorkerPermission")->removeColumn("canWorkorderMph")->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->getEnvironment() == "addressdb") {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Phinx\Migration\AbstractMigration;
|
||||||
|
|
||||||
|
final class AddWorkorderTenantConfigModuleFlags extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
if ($this->getEnvironment() == "thetool") {
|
||||||
|
$table = $this->table('WorkorderTenantConfig');
|
||||||
|
|
||||||
|
$table->addColumn('enableWorkorder', 'boolean', [
|
||||||
|
'default' => true,
|
||||||
|
'null' => false,
|
||||||
|
'after' => 'requireCableType',
|
||||||
|
'comment' => 'Enable Workorder module for this tenant'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$table->addColumn('enableWorkorderMph', 'boolean', [
|
||||||
|
'default' => true,
|
||||||
|
'null' => false,
|
||||||
|
'after' => 'enableWorkorder',
|
||||||
|
'comment' => 'Enable WorkorderMPH module for this tenant'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$table->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
if ($this->getEnvironment() == "thetool") {
|
||||||
|
$this->table('WorkorderTenantConfig')
|
||||||
|
->removeColumn('enableWorkorder')
|
||||||
|
->removeColumn('enableWorkorderMph')
|
||||||
|
->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -146,10 +146,10 @@ class GenieACS {
|
|||||||
return self::getParam($device, $param);
|
return self::getParam($device, $param);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createRemoteUser($deviceId) {
|
public function createRemoteUser($deviceId, $forceRecreate = false) {
|
||||||
$this->log->debug("GenieACS: createRemoteUser called", ['deviceId' => $deviceId]);
|
$this->log->debug("GenieACS: createRemoteUser called", ['deviceId' => $deviceId, 'forceRecreate' => $forceRecreate]);
|
||||||
$cacheKey = "remote_user_" . $deviceId;
|
$cacheKey = "remote_user_" . $deviceId;
|
||||||
if ($cached = $this->getCache($cacheKey)) {
|
if (!$forceRecreate && $cached = $this->getCache($cacheKey)) {
|
||||||
$this->log->debug("GenieACS: Using cached credentials");
|
$this->log->debug("GenieACS: Using cached credentials");
|
||||||
return $cached;
|
return $cached;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -252,4 +252,63 @@ class Helper {
|
|||||||
|
|
||||||
return array_map(fn($owner) => new Address($owner['id']), $results);
|
return array_map(fn($owner) => new Address($owner['id']), $results);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get AddressDB Netzgebiet IDs that a user has access to based on their Network ownership
|
||||||
|
* @param User $user The user to get networks for
|
||||||
|
* @return array Array of addressdb netzgebiet IDs
|
||||||
|
*/
|
||||||
|
public static function getADBNetworksFromUser($user): array {
|
||||||
|
if ($user->isAdmin()) {
|
||||||
|
// Admin has access to all networks
|
||||||
|
$db = FronkDB::singleton(ADDRESSDB_DBHOST, ADDRESSDB_DBUSER, ADDRESSDB_DBPASS, ADDRESSDB_DBNAME);
|
||||||
|
$sql = "SELECT id FROM Netzgebiet WHERE id IS NOT NULL";
|
||||||
|
$result = $db->query($sql);
|
||||||
|
$netzgebiete = $result ? $result->fetch_all(MYSQLI_ASSOC) : [];
|
||||||
|
return array_column($netzgebiete, 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get networks where user's address is the owner
|
||||||
|
$networks = NetworkModel::search(['owner_id' => $user->address_id]);
|
||||||
|
|
||||||
|
// Also check user flags for additional networks
|
||||||
|
$flagNetworkIds = json_decode($user->getFlag("workordermph_networks")->value() ?: '[]', true);
|
||||||
|
if (!empty($flagNetworkIds)) {
|
||||||
|
$additionalNetworks = NetworkModel::search(['id' => $flagNetworkIds]);
|
||||||
|
$networks = array_merge($networks, $additionalNetworks);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract adb_netzgebiet_id from networks
|
||||||
|
$netzgebietIds = [];
|
||||||
|
foreach ($networks as $network) {
|
||||||
|
if ($network->adb_netzgebiet_id) {
|
||||||
|
$netzgebietIds[] = $network->adb_netzgebiet_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_unique(array_filter($netzgebietIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get network owners that have WorkorderMph entries (based on Netzgebiet)
|
||||||
|
* @return array Array of Address objects representing network owners
|
||||||
|
*/
|
||||||
|
public static function getMphNetworkOwners(): array {
|
||||||
|
$db = FronkDB::singleton();
|
||||||
|
$addressDbName = defined('ADDRESSDB_DBNAME') ? ADDRESSDB_DBNAME : 'addressdb';
|
||||||
|
$fronkDbName = FRONKDB_DBNAME;
|
||||||
|
|
||||||
|
$sql = "SELECT DISTINCT a.id, a.company, a.lastname, a.firstname
|
||||||
|
FROM `$fronkDbName`.`WorkorderMph` wm
|
||||||
|
INNER JOIN `$addressDbName`.`Hausnummer` hn ON wm.hausnummerId = hn.id
|
||||||
|
INNER JOIN `$addressDbName`.`Netzgebiet` ng ON hn.netzgebiet_id = ng.id
|
||||||
|
INNER JOIN `$fronkDbName`.`Network` n ON n.adb_netzgebiet_id = ng.id
|
||||||
|
INNER JOIN `$fronkDbName`.`Address` a ON n.owner_id = a.id
|
||||||
|
WHERE a.id IS NOT NULL
|
||||||
|
ORDER BY a.company, a.lastname, a.firstname";
|
||||||
|
|
||||||
|
$results = $db->fetch_all_assoc($db->query($sql)) ?? [];
|
||||||
|
|
||||||
|
return array_map(fn($owner) => new Address($owner['id']), $results);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -71,6 +71,10 @@ const RadiusRouterManager = {
|
|||||||
<i class="fa-duotone fa-sitemap"></i>
|
<i class="fa-duotone fa-sitemap"></i>
|
||||||
<span>Netzwerkstruktur</span>
|
<span>Netzwerkstruktur</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button class="ghost-btn action-btn" @click="openEventLog" :disabled="routerLoading || routerActionLoading || speedtestLoading">
|
||||||
|
<i class="fa-duotone fa-list-timeline"></i>
|
||||||
|
<span>Ereignisprotokoll</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -157,6 +161,12 @@ const RadiusRouterManager = {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mt-4 pt-3" style="border-top: 1px solid var(--border);">
|
||||||
|
<button class="ghost-btn" @click="runRemoteAccess(true)" :disabled="remoteAccessLoading">
|
||||||
|
<i class="fa-duotone fa-rotate"></i>
|
||||||
|
<span>Zugangsdaten neu erstellen</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="table-placeholder" style="height: 200px;">Ein Fehler ist aufgetreten.</div>
|
<div v-else class="table-placeholder" style="height: 200px;">Ein Fehler ist aufgetreten.</div>
|
||||||
</tt-dialog>
|
</tt-dialog>
|
||||||
@@ -173,6 +183,34 @@ const RadiusRouterManager = {
|
|||||||
<div v-else class="table-placeholder" style="min-height: 300px;">Keine Daten verfügbar.</div>
|
<div v-else class="table-placeholder" style="min-height: 300px;">Keine Daten verfügbar.</div>
|
||||||
</tt-dialog>
|
</tt-dialog>
|
||||||
|
|
||||||
|
<!-- Event Log Modal -->
|
||||||
|
<tt-dialog :show="showEventLogModal" title="Ereignisprotokoll" @close="showEventLogModal = false" size="wide">
|
||||||
|
<tt-loading-indicator v-if="eventLogLoading" text="Lade Ereignisprotokoll..." style="min-height: 300px;" />
|
||||||
|
<div v-else-if="eventLogData && eventLogData.length > 0">
|
||||||
|
<div class="table-wrap" style="max-height: 500px; overflow-y: auto;">
|
||||||
|
<table class="tt-table compact">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 100px;">Datum</th>
|
||||||
|
<th style="width: 80px;">Uhrzeit</th>
|
||||||
|
<th style="width: 120px;">Gruppe</th>
|
||||||
|
<th>Nachricht</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(event, idx) in eventLogData" :key="idx">
|
||||||
|
<td class="mono small">{{ event.date }}</td>
|
||||||
|
<td class="mono small">{{ event.time }}</td>
|
||||||
|
<td class="small">{{ event.group }}</td>
|
||||||
|
<td class="small">{{ event.msg }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="table-placeholder" style="min-height: 300px;">Keine Ereignisse verfügbar.</div>
|
||||||
|
</tt-dialog>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
data: () => ({
|
data: () => ({
|
||||||
@@ -197,7 +235,11 @@ const RadiusRouterManager = {
|
|||||||
|
|
||||||
showNetworkStructureModal: false,
|
showNetworkStructureModal: false,
|
||||||
networkStructureLoading: false,
|
networkStructureLoading: false,
|
||||||
rootDevice: null
|
rootDevice: null,
|
||||||
|
|
||||||
|
showEventLogModal: false,
|
||||||
|
eventLogLoading: false,
|
||||||
|
eventLogData: null
|
||||||
}),
|
}),
|
||||||
watch: {
|
watch: {
|
||||||
show: {
|
show: {
|
||||||
@@ -353,20 +395,24 @@ const RadiusRouterManager = {
|
|||||||
};
|
};
|
||||||
poll();
|
poll();
|
||||||
},
|
},
|
||||||
async runRemoteAccess() {
|
async runRemoteAccess(forceRecreate = false) {
|
||||||
if (!this.routerDevice || !this.routerDevice.deviceId) return;
|
if (!this.routerDevice || !this.routerDevice.deviceId) return;
|
||||||
this.showRemoteAccessModal = true;
|
this.showRemoteAccessModal = true;
|
||||||
this.remoteAccessLoading = true;
|
this.remoteAccessLoading = true;
|
||||||
this.remoteAccessStep = 'Konfiguriere Zugriff...';
|
this.remoteAccessStep = forceRecreate ? 'Erstelle neue Zugangsdaten...' : 'Konfiguriere Zugriff...';
|
||||||
this.remoteAccessResult = null;
|
this.remoteAccessResult = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/Radius/genieacsRemoteAccess`, {
|
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/Radius/genieacsRemoteAccess`, {
|
||||||
deviceId: this.routerDevice.deviceId
|
deviceId: this.routerDevice.deviceId,
|
||||||
|
forceRecreate: forceRecreate
|
||||||
});
|
});
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
this.remoteAccessResult = data;
|
this.remoteAccessResult = data;
|
||||||
|
if (forceRecreate) {
|
||||||
|
window.notify('success', 'Neue Zugangsdaten erstellt');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(data.message || "Unbekannter Fehler");
|
throw new Error(data.message || "Unbekannter Fehler");
|
||||||
}
|
}
|
||||||
@@ -396,6 +442,29 @@ const RadiusRouterManager = {
|
|||||||
} finally {
|
} finally {
|
||||||
this.networkStructureLoading = false;
|
this.networkStructureLoading = false;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
async openEventLog() {
|
||||||
|
if (!this.routerDevice || !this.routerDevice.deviceId) return;
|
||||||
|
this.showEventLogModal = true;
|
||||||
|
this.eventLogLoading = true;
|
||||||
|
this.eventLogData = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/Radius/genieacsEventLog`, {
|
||||||
|
deviceId: this.routerDevice.deviceId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.success && data.events) {
|
||||||
|
this.eventLogData = data.events;
|
||||||
|
} else {
|
||||||
|
throw new Error(data.message || "Keine Ereignisse gefunden");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
window.notify('error', error.response?.data?.message || 'Fehler beim Laden des Ereignisprotokolls');
|
||||||
|
} finally {
|
||||||
|
this.eventLogLoading = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ const RadiusUsers = {
|
|||||||
data-tooltip-align="left">
|
data-tooltip-align="left">
|
||||||
<i class="fa-duotone fa-chart-line"></i>
|
<i class="fa-duotone fa-chart-line"></i>
|
||||||
</button>
|
</button>
|
||||||
<button v-if="window.TT_CONFIG.ACS_ENABLED" class="ghost-btn" @click="openRouterManager(item)"
|
<button class="ghost-btn" @click="openRouterManager(item)"
|
||||||
data-tooltip="Router Management" data-tooltip-align="left">
|
data-tooltip="Router Management" data-tooltip-align="left">
|
||||||
<i class="fa-duotone fa-router"></i>
|
<i class="fa-duotone fa-router"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -4,10 +4,7 @@ Vue.component('workorder-mph-admin', {
|
|||||||
<tt-card>
|
<tt-card>
|
||||||
<tt-table-crud ref="table" :crud-config="crudConfig">
|
<tt-table-crud ref="table" :crud-config="crudConfig">
|
||||||
<template v-slot:hausnummerinfo="{ row }">
|
<template v-slot:hausnummerinfo="{ row }">
|
||||||
<div class="small">
|
<span class="small">{{ row.street }} {{ row.hausnummer }}<template v-if="row.stiege">/{{ row.stiege }}</template>, {{ row.plz }} {{ row.city }}</span>
|
||||||
<div><strong>Adresse:</strong> {{ row.street }} {{ row.hausnummer }}<template v-if="row.stiege">/{{ row.stiege }}</template></div>
|
|
||||||
<div><strong>Ort:</strong> {{ row.plz }} {{ row.city }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:status="{ row }">
|
<template v-slot:status="{ row }">
|
||||||
@@ -81,7 +78,7 @@ Vue.component('workorder-mph-admin', {
|
|||||||
<!-- Left Column (1/4): Docs Checkbox, Journal, Review -->
|
<!-- Left Column (1/4): Docs Checkbox, Journal, Review -->
|
||||||
<div class="col-xl-3 col-lg-4">
|
<div class="col-xl-3 col-lg-4">
|
||||||
<div class="mph-details-stack">
|
<div class="mph-details-stack">
|
||||||
<checkbox-documentation :workorder-mph-id="parseInt(row.id)" :is-admin="true"/>
|
<checkbox-documentation :workorder-mph-id="parseInt(row.id)" :is-admin="true" @refresh="refresh"/>
|
||||||
<workorder-mph-journal :journals="journals" :workorder-mph-id="row.id" :is-admin="true" @refresh="refresh"/>
|
<workorder-mph-journal :journals="journals" :workorder-mph-id="row.id" :is-admin="true" @refresh="refresh"/>
|
||||||
<workorder-mph-admin-review :docs="docs" :workorder-mph-id="row.id" @refresh="refresh"/>
|
<workorder-mph-admin-review :docs="docs" :workorder-mph-id="row.id" @refresh="refresh"/>
|
||||||
</div>
|
</div>
|
||||||
@@ -89,7 +86,7 @@ Vue.component('workorder-mph-admin', {
|
|||||||
<!-- Right Column (3/4): Wohneinheiten, Documents -->
|
<!-- Right Column (3/4): Wohneinheiten, Documents -->
|
||||||
<div class="col-xl-9 col-lg-8">
|
<div class="col-xl-9 col-lg-8">
|
||||||
<div class="mph-details-stack">
|
<div class="mph-details-stack">
|
||||||
<wohneinheit-status-manager :workorder-mph-id="parseInt(row.id)" :is-admin="true"/>
|
<wohneinheit-status-manager :workorder-mph-id="parseInt(row.id)" :is-admin="true" @refresh="refresh"/>
|
||||||
<workorder-mph-documents :docs="docs" :workorder-mph-id="row.id" :is-admin="true" @refresh="refresh"/>
|
<workorder-mph-documents :docs="docs" :workorder-mph-id="row.id" :is-admin="true" @refresh="refresh"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -433,6 +433,25 @@
|
|||||||
margin: -12px;
|
margin: -12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Custom Scrollbar for Journal */
|
||||||
|
.mph-journal-list::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mph-journal-list::-webkit-scrollbar-track {
|
||||||
|
background: #f1f3f5;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mph-journal-list::-webkit-scrollbar-thumb {
|
||||||
|
background: #adb5bd;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mph-journal-list::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #868e96;
|
||||||
|
}
|
||||||
|
|
||||||
.mph-journal-item {
|
.mph-journal-item {
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
border-bottom: 1px solid #f1f3f5;
|
border-bottom: 1px solid #f1f3f5;
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ Vue.component('wohneinheit-status-manager', {
|
|||||||
<span><i class="fas fa-building"></i> Wohneinheiten</span>
|
<span><i class="fas fa-building"></i> Wohneinheiten</span>
|
||||||
<div>
|
<div>
|
||||||
<span v-if="loading" class="mr-2"><i class="fas fa-spinner fa-spin text-muted"></i></span>
|
<span v-if="loading" class="mr-2"><i class="fas fa-spinner fa-spin text-muted"></i></span>
|
||||||
<a v-if="hausnummerId" :href="'/AddressDB/view/?id=' + hausnummerId" target="_blank" class="small text-muted">
|
<a v-if="isAdmin && hausnummerId" :href="'/AddressDB/view/?id=' + hausnummerId" target="_blank" class="small text-muted">
|
||||||
<i class="fas fa-external-link-alt mr-1"></i>AddressDB #{{ hausnummerId }}
|
<i class="fas fa-external-link-alt mr-1"></i>AddressDB #{{ hausnummerId }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -238,6 +238,7 @@ Vue.component('wohneinheit-status-manager', {
|
|||||||
workorderMphId: this.workorderMphId, wohneinheitId: we.wohneinheitId, status: we.status, spliceCompleted: we.spliceCompleted ? 1 : 0, tuer: we.tuer, zusatz: we.zusatz
|
workorderMphId: this.workorderMphId, wohneinheitId: we.wohneinheitId, status: we.status, spliceCompleted: we.spliceCompleted ? 1 : 0, tuer: we.tuer, zusatz: we.zusatz
|
||||||
});
|
});
|
||||||
this.$emit('wohneinheit-updated');
|
this.$emit('wohneinheit-updated');
|
||||||
|
this.$emit('refresh');
|
||||||
} catch (e) { window.notify('error', 'Fehler beim Speichern'); } finally { we.saving = false; }
|
} catch (e) { window.notify('error', 'Fehler beim Speichern'); } finally { we.saving = false; }
|
||||||
},
|
},
|
||||||
getStatusText(val) { const o = this.statusOptions.find(opt => opt.value === val); return o ? o.text : ''; },
|
getStatusText(val) { const o = this.statusOptions.find(opt => opt.value === val); return o ? o.text : ''; },
|
||||||
@@ -292,9 +293,12 @@ Vue.component('wohneinheit-status-manager', {
|
|||||||
window.notify('success', `${successCount} Datei(en) hochgeladen`);
|
window.notify('success', `${successCount} Datei(en) hochgeladen`);
|
||||||
this.documentsModal.files = [];
|
this.documentsModal.files = [];
|
||||||
this.documentsModal.uploadDescription = '';
|
this.documentsModal.uploadDescription = '';
|
||||||
this.$refs.weFileInput.value = '';
|
this.$refs.weFileInput.value = '';
|
||||||
const { data } = await axios.get(`${window.TT_CONFIG.BASE_PATH}${basePath}/getWohneinheitDocuments`, { params: { wohneinheitId: this.documentsModal.wohneinheitId } });
|
const { data } = await axios.get(`${window.TT_CONFIG.BASE_PATH}${basePath}/getWohneinheitDocuments`, { params: { wohneinheitId: this.documentsModal.wohneinheitId } });
|
||||||
this.documentsModal.docs = data.docs || [];
|
this.documentsModal.docs = data.docs || [];
|
||||||
|
// Update document count in wohneinheit list
|
||||||
|
const we = this.wohneinheiten.find(w => w.wohneinheitId === this.documentsModal.wohneinheitId);
|
||||||
|
if (we) we.documentCount = this.documentsModal.docs.length;
|
||||||
} else {
|
} else {
|
||||||
window.notify('error', 'Upload fehlgeschlagen');
|
window.notify('error', 'Upload fehlgeschlagen');
|
||||||
}
|
}
|
||||||
@@ -305,9 +309,12 @@ Vue.component('wohneinheit-status-manager', {
|
|||||||
try {
|
try {
|
||||||
const basePath = this.isAdmin ? '/WorkorderMphAdmin' : '/WorkorderMphCompany';
|
const basePath = this.isAdmin ? '/WorkorderMphAdmin' : '/WorkorderMphCompany';
|
||||||
await axios.post(`${window.TT_CONFIG.BASE_PATH}${basePath}/deleteWohneinheitDocument`, { documentationId: file.id });
|
await axios.post(`${window.TT_CONFIG.BASE_PATH}${basePath}/deleteWohneinheitDocument`, { documentationId: file.id });
|
||||||
window.notify('success', 'Gelöscht');
|
window.notify('success', 'Gelöscht');
|
||||||
const { data } = await axios.get(`${window.TT_CONFIG.BASE_PATH}${basePath}/getWohneinheitDocuments`, { params: { wohneinheitId: this.documentsModal.wohneinheitId } });
|
const { data } = await axios.get(`${window.TT_CONFIG.BASE_PATH}${basePath}/getWohneinheitDocuments`, { params: { wohneinheitId: this.documentsModal.wohneinheitId } });
|
||||||
this.documentsModal.docs = data.docs || [];
|
this.documentsModal.docs = data.docs || [];
|
||||||
|
// Update document count in wohneinheit list
|
||||||
|
const we = this.wohneinheiten.find(w => w.wohneinheitId === this.documentsModal.wohneinheitId);
|
||||||
|
if (we) we.documentCount = this.documentsModal.docs.length;
|
||||||
} catch (e) { window.notify('error', 'Fehler beim Löschen'); }
|
} catch (e) { window.notify('error', 'Fehler beim Löschen'); }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -357,6 +364,7 @@ Vue.component('checkbox-documentation', {
|
|||||||
try {
|
try {
|
||||||
const basePath = this.isAdmin ? '/WorkorderMphAdmin' : '/WorkorderMphCompany';
|
const basePath = this.isAdmin ? '/WorkorderMphAdmin' : '/WorkorderMphCompany';
|
||||||
await axios.post(`${window.TT_CONFIG.BASE_PATH}${basePath}/updateCheckboxes`, { workorderMphId: this.workorderMphId, ...this.checkboxes });
|
await axios.post(`${window.TT_CONFIG.BASE_PATH}${basePath}/updateCheckboxes`, { workorderMphId: this.workorderMphId, ...this.checkboxes });
|
||||||
|
this.$emit('refresh');
|
||||||
} catch (e) { window.notify('error', 'Speichern fehlgeschlagen'); } finally { this.saving = false; }
|
} catch (e) { window.notify('error', 'Speichern fehlgeschlagen'); } finally { this.saving = false; }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,11 +4,7 @@ Vue.component('workorder-mph-company', {
|
|||||||
<tt-card>
|
<tt-card>
|
||||||
<tt-table-crud ref="table" :crud-config="crudConfig">
|
<tt-table-crud ref="table" :crud-config="crudConfig">
|
||||||
<template v-slot:hausnummerinfo="{ row }">
|
<template v-slot:hausnummerinfo="{ row }">
|
||||||
<div class="small">
|
<span class="small">{{ row.street }} {{ row.hausnummer }}<template v-if="row.stiege">/{{ row.stiege }}</template>, {{ row.plz }} {{ row.city }}</span>
|
||||||
<div><strong>Adresse:</strong> {{ row.street }} {{ row.hausnummer }}<template v-if="row.stiege">/{{ row.stiege }}</template></div>
|
|
||||||
<div><strong>Ort:</strong> {{ row.plz }} {{ row.city }}</div>
|
|
||||||
<div><strong>Wohneinheiten:</strong> {{ row.wohneinheitCount }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:status="{ row }">
|
<template v-slot:status="{ row }">
|
||||||
@@ -17,59 +13,71 @@ Vue.component('workorder-mph-company', {
|
|||||||
<span class="ml-2">{{ getStatusColumn(row.status).text }}</span>
|
<span class="ml-2">{{ getStatusColumn(row.status).text }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:additionalinfo="{ row }">
|
||||||
|
<div v-if="editingAdditionalInfoId === row.id">
|
||||||
|
<tt-textarea v-model="tempAdditionalInfo" @keydown.esc.native="cancelEdit" rows="3" no-form-group sm ref="editTextarea"/>
|
||||||
|
<div class="mt-2 d-flex justify-content-end">
|
||||||
|
<tt-button text="Abbrechen" @click="cancelEdit" sm additional-class="btn-secondary mr-2"/>
|
||||||
|
<tt-button text="Speichern" @click="updateAdditionalInfo(row)" sm additional-class="btn-success"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="d-flex align-items-start">
|
||||||
|
<span style="white-space: pre-wrap; max-width: 250px; display: inline-block;">{{ row.additionalInfo || '-' }}</span>
|
||||||
|
<tt-button v-if="!['completed', 'cancelled', 'documented'].includes(row.status)"
|
||||||
|
icon="fas fa-edit" @click="startAdditionalInfoEdit(row)"
|
||||||
|
additional-class="btn-link btn-sm p-0 ml-2" title="Notiz bearbeiten"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template v-slot:deadlinedate="{ row }">{{ formatDate(row.deadlineDate) }}</template>
|
<template v-slot:deadlinedate="{ row }">{{ formatDate(row.deadlineDate) }}</template>
|
||||||
|
|
||||||
<template v-slot:appointmentdate="{ row }">
|
<template v-slot:appointmentdate="{ row }">
|
||||||
<div v-if="editingAppointmentId === row.id">
|
<div v-if="!row.appointmentDate && canSchedule(row)">
|
||||||
<tt-date-picker :value="row.appointmentDate" :date-range="false" time-picker
|
<tt-date-picker placeholder="Termin festlegen..." :date-range="false"
|
||||||
@input="scheduleAppointment(row, $event)" @blur="editingAppointmentId = null"
|
@input="scheduleAppointment(row, $event)" sm no-form-group
|
||||||
sm no-form-group/>
|
:additional-props="{ timePicker: true, timePicker24Hour: true, locale: { format: 'DD.MM.YYYY HH:mm' }, drops: 'up' }"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="d-flex align-items-center">
|
<div v-else-if="row.appointmentDate" class="d-flex align-items-center">
|
||||||
<span>{{ formatDate(row.appointmentDate, true) }}</span>
|
<span>{{ formatDate(row.appointmentDate, true) }}</span>
|
||||||
<tt-button v-if="canSchedule(row)" icon="fas fa-edit"
|
<tt-button v-if="canSchedule(row)"
|
||||||
@click="editingAppointmentId = row.id"
|
icon="fas fa-edit" @click="openRescheduleModal(row)"
|
||||||
additional-class="btn-link btn-sm p-0 ml-2" title="Termin planen"/>
|
additional-class="btn-link btn-sm p-0 ml-2" title="Termin ändern"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<span v-else>–</span>
|
||||||
|
|
||||||
<template v-slot:additionalinfo="{ row }">
|
|
||||||
<span style="white-space: pre-wrap; max-width: 250px; display: inline-block;">{{ row.additionalInfo || '-' }}</span>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:expandedRow="{ row }">
|
<template v-slot:expandedRow="{ row }">
|
||||||
<div class="workorder-mph-expanded-wrapper">
|
<workorder-mph-data-provider :workorder-mph-id="row.id" v-slot="{ docs, journals, refresh }">
|
||||||
<!-- Action Buttons -->
|
<div class="workorder-mph-expanded-wrapper">
|
||||||
<div class="mb-3" v-if="!['completed', 'cancelled', 'documented'].includes(row.status)">
|
<!-- Action Buttons -->
|
||||||
<div class="btn-group" role="group">
|
<div class="mb-3" v-if="!['completed', 'cancelled', 'documented'].includes(row.status)">
|
||||||
<tt-button v-if="row.status === 'assigned'" text="Termin planen"
|
<div class="btn-group" role="group">
|
||||||
@click="editingAppointmentId = row.id" icon="fas fa-calendar-plus"
|
<tt-button v-if="row.status === 'scheduled'" text="Arbeit beginnen"
|
||||||
additional-class="btn-primary"/>
|
@click="startWork(row)" icon="fas fa-play" additional-class="btn-success"/>
|
||||||
<tt-button v-if="row.status === 'scheduled'" text="Arbeit beginnen"
|
<tt-button v-if="row.status === 'in_progress'" text="Auftrag abschließen"
|
||||||
@click="startWork(row)" icon="fas fa-play" additional-class="btn-success"/>
|
@click="openCompleteModal(row)" icon="fas fa-check-double"
|
||||||
<tt-button v-if="row.status === 'scheduled'" text="Termin verschieben"
|
additional-class="btn-success"/>
|
||||||
@click="openRescheduleModal(row)" icon="fas fa-calendar-alt"
|
</div>
|
||||||
additional-class="btn-warning"/>
|
|
||||||
<tt-button v-if="row.status === 'in_progress'" text="Auftrag abschließen"
|
|
||||||
@click="openCompleteModal(row)" icon="fas fa-check-double"
|
|
||||||
additional-class="btn-success"/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row g-3">
|
<div class="row g-2">
|
||||||
<div class="col-xl-4 col-lg-6">
|
<!-- Left Column (1/4): Docs Checkbox, Journal -->
|
||||||
<checkbox-documentation :workorder-mph-id="parseInt(row.id)" :is-admin="false"/>
|
<div class="col-xl-3 col-lg-4">
|
||||||
</div>
|
<div class="mph-details-stack">
|
||||||
<div class="col-xl-8 col-lg-6">
|
<checkbox-documentation :workorder-mph-id="parseInt(row.id)" :is-admin="false" @refresh="refresh"/>
|
||||||
<workorder-mph-details-manager :workorder-mph-id="row.id" :is-admin="false"
|
<workorder-mph-journal :journals="journals" :workorder-mph-id="row.id" :is-admin="false" @refresh="refresh"/>
|
||||||
@workorder-completed="$refs.table.$refs.table.refreshTable()"/>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12">
|
<!-- Right Column (3/4): Wohneinheiten, Documents -->
|
||||||
<wohneinheit-status-manager :workorder-mph-id="parseInt(row.id)" :is-admin="false"
|
<div class="col-xl-9 col-lg-8">
|
||||||
@wohneinheit-updated="checkAllWohneinheitenHaveNotes(row.id)"/>
|
<div class="mph-details-stack">
|
||||||
|
<wohneinheit-status-manager :workorder-mph-id="parseInt(row.id)" :is-admin="false" @refresh="refresh"/>
|
||||||
|
<workorder-mph-documents :docs="docs" :workorder-mph-id="row.id" :is-admin="false" @refresh="refresh"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</workorder-mph-data-provider>
|
||||||
</template>
|
</template>
|
||||||
</tt-table-crud>
|
</tt-table-crud>
|
||||||
|
|
||||||
@@ -77,7 +85,8 @@ Vue.component('workorder-mph-company', {
|
|||||||
title="Termin verschieben" @submit="rescheduleAppointment">
|
title="Termin verschieben" @submit="rescheduleAppointment">
|
||||||
<p>Aktueller Termin: <strong>{{ formatDate(rescheduleModalData.currentDate, true) }}</strong></p>
|
<p>Aktueller Termin: <strong>{{ formatDate(rescheduleModalData.currentDate, true) }}</strong></p>
|
||||||
<tt-date-picker label="Neuer Termin" v-model="rescheduleModalData.newDate"
|
<tt-date-picker label="Neuer Termin" v-model="rescheduleModalData.newDate"
|
||||||
:date-range="false" time-picker sm row required/>
|
:date-range="false" sm row required
|
||||||
|
:additional-props="{ timePicker: true, timePicker24Hour: true, locale: { format: 'DD.MM.YYYY HH:mm' }, singleDatePicker: true }"/>
|
||||||
<tt-textarea label="Grund" v-model="rescheduleModalData.reason" sm row required/>
|
<tt-textarea label="Grund" v-model="rescheduleModalData.reason" sm row required/>
|
||||||
</tt-modal>
|
</tt-modal>
|
||||||
|
|
||||||
@@ -94,7 +103,8 @@ Vue.component('workorder-mph-company', {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
window,
|
window,
|
||||||
editingAppointmentId: null,
|
editingAdditionalInfoId: null,
|
||||||
|
tempAdditionalInfo: '',
|
||||||
rescheduleModalData: null,
|
rescheduleModalData: null,
|
||||||
completeModalData: null,
|
completeModalData: null,
|
||||||
crudConfig: {
|
crudConfig: {
|
||||||
@@ -102,7 +112,7 @@ Vue.component('workorder-mph-company', {
|
|||||||
selectable: false,
|
selectable: false,
|
||||||
expandable: true,
|
expandable: true,
|
||||||
customRowClass: (row) => {
|
customRowClass: (row) => {
|
||||||
if (['completed', 'new', 'cancelled'].includes(row.status)) return 'tt-mph-workorder-irrelevant';
|
if (['completed', 'new', 'cancelled', 'archived'].includes(row.status)) return 'tt-mph-workorder-irrelevant';
|
||||||
const deadlineDate = moment.unix(row.deadlineDate);
|
const deadlineDate = moment.unix(row.deadlineDate);
|
||||||
if (!deadlineDate.isValid()) return 'tt-mph-workorder-irrelevant';
|
if (!deadlineDate.isValid()) return 'tt-mph-workorder-irrelevant';
|
||||||
const daysLeft = deadlineDate.diff(moment(), 'days');
|
const daysLeft = deadlineDate.diff(moment(), 'days');
|
||||||
@@ -123,17 +133,46 @@ Vue.component('workorder-mph-company', {
|
|||||||
return window.moment.unix(timestamp).format(withTime ? 'DD.MM.YYYY HH:mm' : 'DD.MM.YYYY');
|
return window.moment.unix(timestamp).format(withTime ? 'DD.MM.YYYY HH:mm' : 'DD.MM.YYYY');
|
||||||
},
|
},
|
||||||
canSchedule(row) {
|
canSchedule(row) {
|
||||||
return ['assigned', 'scheduled'].includes(row.status);
|
return ['assigned', 'scheduled', 'in_progress'].includes(row.status);
|
||||||
},
|
},
|
||||||
async scheduleAppointment(row, newDate) {
|
startAdditionalInfoEdit(row) {
|
||||||
if (!newDate) {
|
this.editingAdditionalInfoId = row.id;
|
||||||
this.editingAppointmentId = null;
|
this.tempAdditionalInfo = row.additionalInfo || '';
|
||||||
|
this.$nextTick(() => this.$refs.editTextarea?.$el.querySelector('textarea').focus());
|
||||||
|
},
|
||||||
|
cancelEdit() {
|
||||||
|
this.editingAdditionalInfoId = null;
|
||||||
|
this.tempAdditionalInfo = '';
|
||||||
|
},
|
||||||
|
async updateAdditionalInfo(row) {
|
||||||
|
if (row.additionalInfo === this.tempAdditionalInfo) {
|
||||||
|
this.cancelEdit();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
const { data } = await axios.post(`${window.TT_CONFIG.BASE_PATH}/WorkorderMphCompany/updateAdditionalInfo`, {
|
||||||
|
workorderMphId: row.id,
|
||||||
|
additionalInfo: this.tempAdditionalInfo
|
||||||
|
});
|
||||||
|
if (data.success) {
|
||||||
|
window.notify('success', data.message);
|
||||||
|
row.additionalInfo = data.newInfo;
|
||||||
|
} else {
|
||||||
|
window.notify('error', data.message || 'Update fehlgeschlagen.');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
window.notify('error', 'Netzwerkfehler.');
|
||||||
|
} finally {
|
||||||
|
this.cancelEdit();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async scheduleAppointment(row, newDate) {
|
||||||
|
if (!newDate) return;
|
||||||
|
|
||||||
const hour = parseInt(moment.unix(newDate).format('H'));
|
const hour = parseInt(moment.unix(newDate).format('H'));
|
||||||
if (hour >= 23 || hour < 1) {
|
if (hour >= 23 || hour < 1) {
|
||||||
window.notify('error', 'Bitte geben Sie eine Uhrzeit zwischen 01:00 und 22:59 an!');
|
window.notify('error', 'Bitte geben Sie eine Uhrzeit zwischen 01:00 und 22:59 an!');
|
||||||
|
this.$refs.table.$refs.table.refreshTable();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,8 +189,6 @@ Vue.component('workorder-mph-company', {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
window.notify('error', 'Netzwerkfehler.');
|
window.notify('error', 'Netzwerkfehler.');
|
||||||
} finally {
|
|
||||||
this.editingAppointmentId = null;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openRescheduleModal(row) {
|
openRescheduleModal(row) {
|
||||||
@@ -169,8 +206,7 @@ Vue.component('workorder-mph-company', {
|
|||||||
|
|
||||||
const hour = parseInt(moment.unix(this.rescheduleModalData.newDate).format('H'));
|
const hour = parseInt(moment.unix(this.rescheduleModalData.newDate).format('H'));
|
||||||
if (hour >= 23 || hour < 1) {
|
if (hour >= 23 || hour < 1) {
|
||||||
window.notify('error', 'Bitte geben Sie eine Uhrzeit zwischen 01:00 und 22:59 an!');
|
return window.notify('error', 'Bitte geben Sie eine Uhrzeit zwischen 01:00 und 22:59 an!');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -225,10 +261,6 @@ Vue.component('workorder-mph-company', {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
window.notify('error', 'Ein Netzwerkfehler ist aufgetreten.');
|
window.notify('error', 'Ein Netzwerkfehler ist aufgetreten.');
|
||||||
}
|
}
|
||||||
},
|
|
||||||
async checkAllWohneinheitenHaveNotes(workorderId) {
|
|
||||||
// This is called when a wohneinheit is updated
|
|
||||||
// Could be used to enable/disable the complete button
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -78,6 +78,11 @@ Vue.component('workorder-tenant-config', {
|
|||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h6 class="mb-3">Optionen</h6>
|
<h6 class="mb-3">Optionen</h6>
|
||||||
<div v-if="editingId === config.id">
|
<div v-if="editingId === config.id">
|
||||||
|
<tt-checkbox label="Workorder aktivieren"
|
||||||
|
v-model="editableItem.enableWorkorder" sm/>
|
||||||
|
<tt-checkbox label="WorkorderMPH aktivieren"
|
||||||
|
v-model="editableItem.enableWorkorderMph" sm/>
|
||||||
|
<hr>
|
||||||
<tt-checkbox label="Dokumentation für Tiefbau erforderlich"
|
<tt-checkbox label="Dokumentation für Tiefbau erforderlich"
|
||||||
v-model="editableItem.civilEngineeringDocsRequired" sm/>
|
v-model="editableItem.civilEngineeringDocsRequired" sm/>
|
||||||
<tt-checkbox label="Kabellänge erforderlich"
|
<tt-checkbox label="Kabellänge erforderlich"
|
||||||
@@ -86,6 +91,9 @@ Vue.component('workorder-tenant-config', {
|
|||||||
v-model="editableItem.requireCableType" sm/>
|
v-model="editableItem.requireCableType" sm/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
|
<p>Workorder: <strong>{{ config.enableWorkorder ? 'Aktiviert' : 'Deaktiviert' }}</strong></p>
|
||||||
|
<p>WorkorderMPH: <strong>{{ config.enableWorkorderMph ? 'Aktiviert' : 'Deaktiviert' }}</strong></p>
|
||||||
|
<hr>
|
||||||
<p>Tiefbau-Doku: <strong>{{ config.civilEngineeringDocsRequired ? 'Ja' : 'Nein' }}</strong></p>
|
<p>Tiefbau-Doku: <strong>{{ config.civilEngineeringDocsRequired ? 'Ja' : 'Nein' }}</strong></p>
|
||||||
<p>Kabellänge-Doku: <strong>{{ config.requireCableLength ? 'Ja' : 'Nein' }}</strong></p>
|
<p>Kabellänge-Doku: <strong>{{ config.requireCableLength ? 'Ja' : 'Nein' }}</strong></p>
|
||||||
<p>Kabeltyp-Doku: <strong>{{ config.requireCableType ? 'Ja' : 'Nein' }}</strong></p>
|
<p>Kabeltyp-Doku: <strong>{{ config.requireCableType ? 'Ja' : 'Nein' }}</strong></p>
|
||||||
@@ -324,7 +332,9 @@ Vue.component('workorder-tenant-config', {
|
|||||||
workorderActiveFilters: '{}',
|
workorderActiveFilters: '{}',
|
||||||
civilEngineeringDocsRequired: 0,
|
civilEngineeringDocsRequired: 0,
|
||||||
requireCableLength: 0,
|
requireCableLength: 0,
|
||||||
requireCableType: 0
|
requireCableType: 0,
|
||||||
|
enableWorkorder: 1,
|
||||||
|
enableWorkorderMph: 1
|
||||||
}
|
}
|
||||||
: {visibleForAddressId: []};
|
: {visibleForAddressId: []};
|
||||||
this.showModal = true;
|
this.showModal = true;
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ Vue.component('tt-table', {
|
|||||||
</div>
|
</div>
|
||||||
<!-- @formatter:off -->
|
<!-- @formatter:off -->
|
||||||
<tt-input v-if="column.filter === 'search' && !disableFiltering" v-model="filters[column.key]" sm/>
|
<tt-input v-if="column.filter === 'search' && !disableFiltering" v-model="filters[column.key]" sm/>
|
||||||
<tt-icon-select v-else-if="column.filter === 'iconSelect' && !disableFiltering" :options="column.filterOptions" v-model="filters[column.key]" sm :multiple="column.filterOptions.length > 2 && this.ssr === true" sm/>
|
<tt-icon-select v-else-if="column.filter === 'iconSelect' && !disableFiltering" :options="column.filterOptions" v-model="filters[column.key]" sm :multiple="column.filterOptions.length > 2 && ssr === true" sm/>
|
||||||
<tt-number-range v-else-if="column.filter === 'numberRange' && !disableFiltering" :returnText="!ssr" v-model="filters[column.key]" sm/>
|
<tt-number-range v-else-if="column.filter === 'numberRange' && !disableFiltering" :returnText="!ssr" v-model="filters[column.key]" sm/>
|
||||||
<tt-select v-else-if="column.filter === 'select' && !disableFiltering" :options="[...column.filterOptions]" v-model="filters[column.key]" sm multiple/>
|
<tt-select v-else-if="column.filter === 'select' && !disableFiltering" :options="[...column.filterOptions]" v-model="filters[column.key]" sm multiple/>
|
||||||
<tt-autocomplete v-else-if="column.filter === 'autocomplete' && !disableFiltering" :api-url="column.filterOptions" v-model="filters[column.key]" sm/>
|
<tt-autocomplete v-else-if="column.filter === 'autocomplete' && !disableFiltering" :api-url="column.filterOptions" v-model="filters[column.key]" sm/>
|
||||||
|
|||||||
109
scripts/workorder-mph-create-from-hausnummer.php
Normal file
109
scripts/workorder-mph-create-from-hausnummer.php
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
#!/usr/bin/php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require("../config/config.php");
|
||||||
|
|
||||||
|
define('FRONKDB_SQLDEBUG',false);
|
||||||
|
error_reporting(E_ALL & ~(E_NOTICE | E_STRICT | E_DEPRECATED));
|
||||||
|
|
||||||
|
require_once(LIBDIR."/mvcfronk/mfRouter/mfRouter.php");
|
||||||
|
require_once(LIBDIR."/mvcfronk/mfBase/mfBaseModel.php");
|
||||||
|
require_once(LIBDIR."/mvcfronk/mfBase/mfBaseController.php");
|
||||||
|
|
||||||
|
$me = new User(1);
|
||||||
|
|
||||||
|
echo "[" . date('Y-m-d H:i:s') . "] Starting WorkorderMph creation from Hausnummer\n";
|
||||||
|
|
||||||
|
$db = FronkDB::singleton(ADDRESSDB_DBHOST, ADDRESSDB_DBUSER, ADDRESSDB_DBPASS, ADDRESSDB_DBNAME);
|
||||||
|
|
||||||
|
// Build netzgebiet filter
|
||||||
|
$netzgebietIds = defined('TT_WORKORDER_MPH_NETZGEBIET_IDS') ? TT_WORKORDER_MPH_NETZGEBIET_IDS : [];
|
||||||
|
$netzgebietFilter = '';
|
||||||
|
if (!empty($netzgebietIds)) {
|
||||||
|
$escapedIds = array_map(fn($id) => $db->escape($id), $netzgebietIds);
|
||||||
|
$netzgebietFilter = " AND hn.netzgebiet_id IN (" . implode(',', $escapedIds) . ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find Hausnummer with >2 Wohneinheiten and state not in grossplaning/not2connect
|
||||||
|
$sql = "
|
||||||
|
SELECT hn.id, hn.netzgebiet_id, COUNT(we.id) as we_count
|
||||||
|
FROM Hausnummer hn
|
||||||
|
LEFT JOIN Wohneinheit we ON hn.id = we.hausnummer_id
|
||||||
|
WHERE hn.rimo_ex_state NOT IN ('grossplaning', 'not2connect')
|
||||||
|
AND hn.rimo_op_state NOT IN ('grossplaning', 'not2connect')
|
||||||
|
$netzgebietFilter
|
||||||
|
GROUP BY hn.id
|
||||||
|
HAVING we_count > 2
|
||||||
|
";
|
||||||
|
|
||||||
|
$result = $db->query($sql);
|
||||||
|
$hausnummern = $result ? $result->fetch_all(MYSQLI_ASSOC) : [];
|
||||||
|
|
||||||
|
echo "[" . date('Y-m-d H:i:s') . "] Found " . count($hausnummern) . " Hausnummern with >2 Wohneinheiten\n";
|
||||||
|
|
||||||
|
// Get valid hausnummer IDs
|
||||||
|
$validHausnummerIds = array_column($hausnummern, 'id');
|
||||||
|
|
||||||
|
$createdCount = 0;
|
||||||
|
$reactivatedCount = 0;
|
||||||
|
|
||||||
|
foreach ($hausnummern as $hn) {
|
||||||
|
// Check if WorkorderMph already exists
|
||||||
|
$existing = WorkorderMphModel::getFirst(['hausnummerId' => $hn['id']]);
|
||||||
|
|
||||||
|
if (!$existing) {
|
||||||
|
// Create new WorkorderMph
|
||||||
|
WorkorderMphModel::create([
|
||||||
|
'hausnummerId' => $hn['id'],
|
||||||
|
'status' => 'new',
|
||||||
|
'create' => time(),
|
||||||
|
'createBy' => 1 // System user
|
||||||
|
]);
|
||||||
|
$createdCount++;
|
||||||
|
echo "[" . date('Y-m-d H:i:s') . "] Created new WorkorderMph for Hausnummer ID {$hn['id']}\n";
|
||||||
|
} elseif ($existing->status === 'archived') {
|
||||||
|
// Reactivate archived workorder
|
||||||
|
$existing->status = 'new';
|
||||||
|
$existing->companyId = null;
|
||||||
|
$existing->deadlineDate = null;
|
||||||
|
$existing->appointmentDate = null;
|
||||||
|
WorkorderMphModel::update((array)$existing);
|
||||||
|
|
||||||
|
WorkorderMphJournalModel::create([
|
||||||
|
'workorderMphId' => $existing->id,
|
||||||
|
'text' => 'Arbeitsauftrag wurde automatisch reaktiviert.',
|
||||||
|
'statusChange' => 'archiviert -> neu',
|
||||||
|
'create' => time(),
|
||||||
|
'createBy' => 1,
|
||||||
|
]);
|
||||||
|
$reactivatedCount++;
|
||||||
|
echo "[" . date('Y-m-d H:i:s') . "] Reactivated WorkorderMph #{$existing->id} for Hausnummer ID {$hn['id']}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "[" . date('Y-m-d H:i:s') . "] Created: $createdCount, Reactivated: $reactivatedCount\n";
|
||||||
|
|
||||||
|
// Archive workorders for Hausnummer that are no longer in allowed netzgebiete or don't meet criteria
|
||||||
|
if (!empty($netzgebietIds)) {
|
||||||
|
$allWorkorders = WorkorderMphModel::getAll(['status' => ['new', 'assigned', 'scheduled', 'in_progress']]);
|
||||||
|
$archivedCount = 0;
|
||||||
|
foreach ($allWorkorders as $workorder) {
|
||||||
|
if (!in_array($workorder->hausnummerId, $validHausnummerIds)) {
|
||||||
|
$workorder->status = 'archived';
|
||||||
|
WorkorderMphModel::update((array)$workorder);
|
||||||
|
|
||||||
|
WorkorderMphJournalModel::create([
|
||||||
|
'workorderMphId' => $workorder->id,
|
||||||
|
'text' => 'Arbeitsauftrag automatisch archiviert (Netzgebiet deaktiviert oder Kriterien nicht mehr erfüllt).',
|
||||||
|
'statusChange' => 'active -> archived',
|
||||||
|
'create' => time(),
|
||||||
|
'createBy' => 1,
|
||||||
|
]);
|
||||||
|
$archivedCount++;
|
||||||
|
echo "[" . date('Y-m-d H:i:s') . "] Archived WorkorderMph #{$workorder->id}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo "[" . date('Y-m-d H:i:s') . "] Archived: $archivedCount\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "[" . date('Y-m-d H:i:s') . "] WorkorderMph creation/update completed successfully\n";
|
||||||
Reference in New Issue
Block a user