312 lines
15 KiB
PHP
312 lines
15 KiB
PHP
<?php
|
|
|
|
class WorkorderMphAdminController extends WorkorderMphBaseController
|
|
{
|
|
protected string $headerTitle = 'MPH Arbeitsaufträge Verwaltung';
|
|
protected bool $createText = false;
|
|
protected array $permissionCheck = ['WorkorderMphAdmin'];
|
|
|
|
protected array $columns = [
|
|
['key' => 'id', 'text' => 'Auftrags-Nr.', 'table' => ['sortable' => true, 'filter' => 'numberRange']],
|
|
['key' => 'hausnummerInfo', 'text' => 'Adresse', 'modal' => false, 'table' => ['filter' => 'search', 'sortable' => false]],
|
|
['key' => 'netzgebietName', 'text' => 'Netzgebiet', 'modal' => false, 'table' => ['filter' => 'search', 'sortable' => true]],
|
|
['key' => 'companyName', 'text' => 'Firma', 'modal' => false, 'table' => ['filter' => 'search', 'sortable' => true]],
|
|
['key' => 'wohneinheitCount', 'text' => 'WE', 'modal' => false, 'table' => ['sortable' => false]],
|
|
['key' => 'additionalInfo', 'text' => 'Notiz', 'modal' => false, 'table' => ['sortable' => true]],
|
|
['key' => 'deadlineDate', 'text' => 'Deadline', 'modal' => false, 'table' => ['filter' => 'date', 'sortable' => true]],
|
|
['key' => 'appointmentDate', 'text' => 'Termin', 'modal' => false, 'table' => ['filter' => 'date', 'sortable' => true]],
|
|
];
|
|
|
|
protected function prepareCrudConfig()
|
|
{
|
|
$hausnummerInfoColIdx = array_search('hausnummerInfo', array_column($this->columns, 'key'));
|
|
array_splice($this->columns, $hausnummerInfoColIdx + 1, 0, [$this->statusColumn]);
|
|
}
|
|
|
|
public function indexAction()
|
|
{
|
|
$this->createWorkordersFromHausnummer();
|
|
parent::indexAction();
|
|
}
|
|
|
|
protected function getAction()
|
|
{
|
|
$pagination = $this->postData['pagination'] ?? ['page' => 1, 'per_page' => 10];
|
|
$filters = $this->postData['filters'] ?? [];
|
|
$order = $this->postData['order'] ?? [];
|
|
|
|
$db = FronkDB::singleton();
|
|
$fronkDbName = FRONKDB_DBNAME;
|
|
$addressDbName = defined('ADDRESSDB_DBNAME') ? ADDRESSDB_DBNAME : 'addressdb';
|
|
|
|
$whereClauses = "WHERE 1=1";
|
|
|
|
if (empty($filters['status'])) {
|
|
$whereClauses .= " AND w.status NOT IN ('completed', 'cancelled', 'archived')";
|
|
} else {
|
|
$whereClauses .= Helper::generateFilterCondition($filters['status'], 'w.status', true);
|
|
}
|
|
|
|
if (!empty($filters['id'])) $whereClauses .= Helper::generateFilterCondition($filters['id'], 'w.id', true);
|
|
if (!empty($filters['hausnummerInfo'])) {
|
|
$searchColumns = "str.name|hn.hausnummer|hn.stiege|plz.plz|ort.name|w.additionalInfo";
|
|
$whereClauses .= Helper::generateFilterCondition($filters['hausnummerInfo'], $searchColumns);
|
|
}
|
|
if (!empty($filters['netzgebietName'])) $whereClauses .= Helper::generateFilterCondition($filters['netzgebietName'], 'ng.name');
|
|
if (!empty($filters['companyName'])) $whereClauses .= Helper::generateFilterCondition($filters['companyName'], 'c.name');
|
|
if (!empty($filters['deadlineDate'])) $whereClauses .= Helper::generateFilterCondition($filters['deadlineDate'], 'w.deadlineDate');
|
|
if (!empty($filters['additionalInfo'])) $whereClauses .= Helper::generateFilterCondition($filters['additionalInfo'], 'w.additionalInfo');
|
|
|
|
$sql = "
|
|
SELECT
|
|
w.id, w.status, w.deadlineDate, w.appointmentDate, w.companyId, w.additionalInfo,
|
|
IFNULL(c.name, 'Nicht zugewiesen') as companyName,
|
|
CONCAT_WS(' ', str.name, hn.hausnummer, hn.stiege) as hausnummerInfo,
|
|
str.name as street, hn.hausnummer, hn.stiege, plz.plz, ort.name as city,
|
|
IFNULL(ng.name, '-') as netzgebietName,
|
|
(SELECT COUNT(*) FROM `$addressDbName`.`Wohneinheit` we WHERE we.hausnummer_id = hn.id) as wohneinheitCount
|
|
FROM `$fronkDbName`.`WorkorderMph` w
|
|
LEFT JOIN `$fronkDbName`.`WorkorderCompany` c ON w.companyId = c.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`.`Plz` plz ON hn.plz_id = plz.id
|
|
LEFT JOIN `$addressDbName`.`Ortschaft` ort ON hn.ortschaft_id = ort.id
|
|
LEFT JOIN `$addressDbName`.`Netzgebiet` ng ON hn.netzgebiet_id = ng.id
|
|
$whereClauses
|
|
";
|
|
|
|
$orderBy = "";
|
|
if (!empty($order['key'])) {
|
|
$sortableColumns = ['id', 'status', 'deadlineDate', 'companyName', 'additionalInfo', 'appointmentDate', 'netzgebietName'];
|
|
if (in_array($order['key'], $sortableColumns)) {
|
|
$sortOrder = (strtoupper($order['order']) === 'DESC') ? 'DESC' : 'ASC';
|
|
$orderBy = " ORDER BY " . $db->escape($order['key']) . " " . $sortOrder;
|
|
}
|
|
}
|
|
if (empty($orderBy)) $orderBy = " ORDER BY CASE WHEN w.deadlineDate IS NULL THEN 1 ELSE 0 END, w.deadlineDate ASC";
|
|
|
|
$sql .= $orderBy;
|
|
|
|
// Get total count
|
|
$countSql = "SELECT COUNT(*) as count FROM `$fronkDbName`.`WorkorderMph` w
|
|
LEFT JOIN `$fronkDbName`.`WorkorderCompany` c ON w.companyId = c.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`.`Plz` plz ON hn.plz_id = plz.id
|
|
LEFT JOIN `$addressDbName`.`Ortschaft` ort ON hn.ortschaft_id = ort.id
|
|
LEFT JOIN `$addressDbName`.`Netzgebiet` ng ON hn.netzgebiet_id = ng.id
|
|
$whereClauses";
|
|
$totalCount = $db->query($countSql)->fetch_assoc()['count'];
|
|
|
|
// Add pagination
|
|
if ($pagination['per_page'] !== null) {
|
|
$sql .= " LIMIT " . intval($pagination['per_page']) . " OFFSET " . intval(($pagination['page'] - 1) * $pagination['per_page']);
|
|
}
|
|
|
|
$result = $db->query($sql);
|
|
$rows = $result ? $result->fetch_all(MYSQLI_ASSOC) : [];
|
|
|
|
self::returnJson([
|
|
'rows' => $rows,
|
|
'pagination' => [
|
|
'page' => $pagination['page'],
|
|
'per_page' => $pagination['per_page'],
|
|
'total_rows' => $totalCount,
|
|
'total_pages' => ceil($totalCount / $pagination['per_page']),
|
|
'filtered_available' => $totalCount
|
|
]
|
|
]);
|
|
}
|
|
|
|
protected function getCompaniesAction()
|
|
{
|
|
$companies = WorkorderCompanyModel::getAll();
|
|
self::returnJson(array_map(fn($c) => ['value' => $c->id, 'text' => $c->name], $companies));
|
|
}
|
|
|
|
protected function assignWorkorderAction()
|
|
{
|
|
if (empty($this->postData['workorderId']) || empty($this->postData['companyId'])) self::sendError("Erforderliche Felder fehlen.");
|
|
$deadline = !empty($this->postData['deadlineDate']) ? $this->postData['deadlineDate'] : strtotime('+6 weeks');
|
|
|
|
$workorder = WorkorderMphModel::get($this->postData['workorderId']);
|
|
if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
|
|
|
|
$oldStatus = $workorder->status;
|
|
$oldCompanyId = $workorder->companyId;
|
|
|
|
$workorder->companyId = $this->postData['companyId'];
|
|
$workorder->status = 'assigned';
|
|
$workorder->assignmentDate = time();
|
|
$workorder->deadlineDate = $deadline;
|
|
|
|
WorkorderMphModel::update((array)$workorder);
|
|
|
|
$company = WorkorderCompanyModel::get($this->postData['companyId']);
|
|
$statusChange = $this->getStatusText($oldStatus) . " -> " . $this->getStatusText('assigned');
|
|
|
|
WorkorderMphJournalModel::create([
|
|
'workorderMphId' => $workorder->id,
|
|
'text' => "Arbeitsauftrag zugewiesen an: " . ($company ? $company->name : "Firma ID " . $this->postData['companyId']),
|
|
'statusChange' => $statusChange,
|
|
'create' => time(),
|
|
'createBy' => $this->user->id,
|
|
]);
|
|
|
|
self::returnJson(['success' => true, 'message' => 'Arbeitsauftrag erfolgreich zugewiesen.']);
|
|
}
|
|
|
|
protected function updateDeadlineAction()
|
|
{
|
|
if (empty($this->postData['workorderId']) || empty($this->postData['deadlineDate'])) self::sendError("Erforderliche Felder fehlen.");
|
|
|
|
$workorder = WorkorderMphModel::get($this->postData['workorderId']);
|
|
if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
|
|
|
|
$workorder->deadlineDate = $this->postData['deadlineDate'];
|
|
WorkorderMphModel::update((array)$workorder);
|
|
WorkorderMphJournalModel::create([
|
|
'workorderMphId' => $workorder->id,
|
|
'text' => 'Deadline geändert auf ' . date('d.m.Y', $this->postData['deadlineDate']) . '.',
|
|
'create' => time(),
|
|
'createBy' => $this->user->id
|
|
]);
|
|
self::returnJson(['success' => true, 'message' => 'Deadline erfolgreich aktualisiert.']);
|
|
}
|
|
|
|
protected function acceptDocumentationAction()
|
|
{
|
|
if (empty($this->postData['workorderId'])) self::sendError("Arbeitsauftrags-ID fehlt.");
|
|
$workorder = WorkorderMphModel::get($this->postData['workorderId']);
|
|
if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
|
|
if ($workorder->status !== 'documented') self::sendError("Die Dokumentation muss zuerst von der Firma als fertig markiert werden.");
|
|
|
|
$oldStatus = $workorder->status;
|
|
$workorder->status = 'completed';
|
|
WorkorderMphModel::update((array)$workorder);
|
|
WorkorderMphJournalModel::create([
|
|
'workorderMphId' => $workorder->id,
|
|
'text' => 'Dokumentation akzeptiert und Arbeitsauftrag abgeschlossen.',
|
|
'statusChange' => $this->getStatusText($oldStatus) . " -> " . $this->getStatusText('completed'),
|
|
'create' => time(),
|
|
'createBy' => $this->user->id,
|
|
]);
|
|
self::returnJson(['success' => true, 'message' => 'Dokumentation akzeptiert und Arbeitsauftrag abgeschlossen.']);
|
|
}
|
|
|
|
/**
|
|
* Background task: Creates WorkorderMph from Hausnummer with >2 Wohneinheiten
|
|
* and RIMO state not in grossplaning/not2connect
|
|
*/
|
|
private function createWorkordersFromHausnummer()
|
|
{
|
|
$lockFile = TEMP_DIR . "/task_create_workorder_mph.lock";
|
|
if (file_exists($lockFile) && (time() - intval(file_get_contents($lockFile))) < 300) {
|
|
return; // Run only every 5 minutes
|
|
}
|
|
|
|
$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) : [];
|
|
|
|
// Get valid hausnummer IDs
|
|
$validHausnummerIds = array_column($hausnummern, 'id');
|
|
|
|
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
|
|
]);
|
|
} 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' => $this->getStatusText('archived') . " -> " . $this->getStatusText('new'),
|
|
'create' => time(),
|
|
'createBy' => 1,
|
|
]);
|
|
}
|
|
}
|
|
|
|
// 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']]);
|
|
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,
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
|
|
file_put_contents($lockFile, time());
|
|
}
|
|
|
|
protected function cancelWorkorderAction()
|
|
{
|
|
if (empty($this->postData['workorderId'])) self::sendError("Arbeitsauftrags-ID fehlt.");
|
|
|
|
$workorder = WorkorderMphModel::get($this->postData['workorderId']);
|
|
if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
|
|
|
|
$oldStatus = $workorder->status;
|
|
$workorder->status = 'cancelled';
|
|
WorkorderMphModel::update((array)$workorder);
|
|
|
|
$reason = !empty($this->postData['reason']) ? $this->postData['reason'] : 'Kein Grund angegeben';
|
|
|
|
WorkorderMphJournalModel::create([
|
|
'workorderMphId' => $workorder->id,
|
|
'text' => "Arbeitsauftrag storniert. Grund: " . $reason,
|
|
'statusChange' => $this->getStatusText($oldStatus) . " -> " . $this->getStatusText('cancelled'),
|
|
'create' => time(),
|
|
'createBy' => $this->user->id,
|
|
]);
|
|
|
|
self::returnJson(['success' => true, 'message' => 'Arbeitsauftrag wurde storniert.']);
|
|
}
|
|
}
|