diff --git a/application/RMLWorkorderAdmin/RMLWorkorderAdminController.php b/application/RMLWorkorderAdmin/RMLWorkorderAdminController.php
index f45a72db7..71a95fac2 100644
--- a/application/RMLWorkorderAdmin/RMLWorkorderAdminController.php
+++ b/application/RMLWorkorderAdmin/RMLWorkorderAdminController.php
@@ -1,375 +1,8 @@
'id', 'text' => 'Auftrags-Nr.', 'table' => ['sortable' => true]],
- ['key' => 'preordercampaign_id', 'text' => 'Kampagne', 'modal' => false, 'table' => ['filter' => 'select']],
- ['key' => 'preorderInfo', 'text' => 'Kunde', 'modal' => false, 'table' => ['sortable' => false]],
- ['key' => 'rimo_fcp_name', 'text' => 'FCP', 'modal' => false, 'table' => ['sortable' => true]],
- ['key' => 'companyName', 'text' => 'Zugewiesene Firma', 'modal' => false, 'table' => ['sortable' => true]],
- ['key' => 'status', 'text' => 'Status', 'modal' => false, 'table' => ['filter' => 'iconSelect', 'filterOptions' => [
- ['value' => 'new', 'text' => 'Neu', 'icon' => 'fas fa-star text-primary'],
- ['value' => 'assigned', 'text' => 'Zugewiesen', 'icon' => 'fas fa-user-check text-info'],
- ['value' => 'scheduled', 'text' => 'Geplant', 'icon' => 'fas fa-calendar-check text-warning'],
- ['value' => 'correction_requested', 'text' => 'Korrektur angefordert', 'icon' => 'fas fa-exclamation-triangle text-danger'],
- ['value' => 'intervention_required', 'text' => 'Eingriff erforderlich', 'icon' => 'fas fa-times-circle text-danger'],
- ['value' => 'problem_solved', 'text' => 'Problem gelöst', 'icon' => 'fas fa-check-circle text-success'],
- ['value' => 'documented', 'text' => 'Dokumentiert', 'icon' => 'fas fa-file-alt text-success'],
- ['value' => 'completed', 'text' => 'Abgeschlossen', 'icon' => 'fas fa-check-double text-secondary'],
- ['value' => 'cancelled', 'text' => 'Abgebrochen', 'icon' => 'fas fa-ban text-danger'],
- ]]],
- ['key' => 'additionalInfo', 'text' => 'Notiz', 'modal' => false, 'table' => ['sortable' => true]],
- ['key' => 'deadlineDate', 'text' => 'Deadline', 'modal' => false, 'table' => ['filter' => 'date', 'sortable' => true]],
- ];
-
- private function getStatusText(string $statusKey): string
- {
- foreach ($this->columns as $column) {
- if ($column['key'] === 'status') {
- foreach ($column['table']['filterOptions'] as $option) {
- if ($option['value'] === $statusKey) {
- return $option['text'];
- }
- }
- }
- }
- return ucfirst(str_replace('_', ' ', $statusKey)); // Fallback
- }
-
- protected function indexAction()
- {
- $campaigns = Helper::getPreorderCampaignFromUser($this->user, true);
- $this->columns[array_search('preordercampaign_id', array_column($this->columns, 'key'))]['table']['filterOptions'] = array_map(
- fn($c) => ['value' => $c->id, 'text' => $c->name],
- $campaigns
- );
-
- $this->createWorkordersFromPreorders();
- Helper::renderVue($this, 'RMLWorkorderAdmin', $this->headerTitle, [
- "CRUD_CONFIG" => $this->getCrudConfig(),
- "TABLE_URL" => $this::getUrl("RMLWorkorderAdmin/get"),
- ]);
- }
-
- protected function getAction()
- {
- $json = json_decode(file_get_contents('php://input'), true);
- $pagination = $json['pagination'] ?? ['page' => 1, 'per_page' => 10];
- $filters = $json['filters'] ?? [];
- $order = $json['order'] ?? [];
-
- $allowedCampaignIds = Helper::getPreorderCampaignFromUser($this->user);
-
- if (empty($allowedCampaignIds)) {
- self::returnJson([
- 'rows' => [],
- 'pagination' => array_merge($pagination, ['total_rows' => 0, 'total_pages' => 0, 'filtered_available' => 0])
- ]);
- return;
- }
-
- $limit = $pagination['per_page'];
- $offset = ($pagination['page'] - 1) * $limit;
-
- $workorders = RMLWorkorderModel::getAdminWorkorders($filters, $limit, $offset, $order, $allowedCampaignIds);
- $totalCount = RMLWorkorderModel::countAdminWorkorders($filters, $allowedCampaignIds);
-
- $rows = array_map(function ($workorder) {
- $row = (array)$workorder;
- $row['companyName'] ??= 'Nicht zugewiesen';
- return $row;
- }, $workorders);
-
- self::returnJson([
- 'rows' => $rows,
- 'pagination' => [
- 'page' => $pagination['page'],
- 'per_page' => $pagination['per_page'],
- 'total_rows' => $totalCount,
- 'total_pages' => ceil($totalCount / $limit),
- 'filtered_available' => $totalCount
- ]
- ]);
- }
-
- private function createWorkordersFromPreorders()
- {
- $configs = RMLWorkorderTenantConfigModel::getAll();
- foreach ($configs as $config) {
- $filters = json_decode($config->workorderCreationFilters, true);
- if (empty($filters)) continue;
-
- $networks = NetworkModel::getAll(['owner_id' => $config->addressId]);
- if (empty($networks)) continue;
-
- $tenantCampaigns = array_map(fn($n) => $n->id, PreordercampaignModel::getAll(['network_id' => array_map(fn($n) => $n->id, $networks)]));
- if (empty($tenantCampaigns)) continue;
-
- $filters['preordercampaign_id'] = $tenantCampaigns;
-
- $newPreorders = PreorderModel::searchActive($filters);
- if (empty($newPreorders)) continue;
-
- foreach ($newPreorders as $preorder) {
- if (!RMLWorkorderModel::getFirst(['preorderId' => $preorder->id])) {
- RMLWorkorderModel::create([
- 'preorderId' => $preorder->id,
- 'clusterId' => $preorder->preordercampaign_id,
- 'status' => 'new',
- 'create' => time(),
- 'createBy' => 0 // System User
- ]);
- }
- }
- }
- }
-
- protected function getDocumentationAction()
- {
- if (empty($this->request->workorderId)) self::sendError("Arbeitsauftrags-ID fehlt.");
-
- $docs = RMLWorkorderDocumentationModel::getAll(['workorderId' => $this->request->workorderId], null, 0, ['key' => 'create', 'order' => 'ASC']);
- $journals = RMLWorkorderJournalModel::getAll(['workorderId' => $this->request->workorderId], null, 0, ['key' => 'create', 'order' => 'DESC']);
-
- $translationMap = [
- 'photo_hup_mounted' => 'Foto_montierter_HÜP', 'photo_hup_open' => 'Foto_offener_HÜP',
- 'photo_splice_cassette_hup' => 'Foto_Spleißkassette_HÜP', 'photo_splice_cassette_fcp' => 'Foto_Spleißkassette_FCP',
- 'photo_hup_closed_stickers' => 'Foto_geschlossener_HÜP_mit_Aufklebern', 'photo_fcp_labeled' => 'Foto_FCP_beschriftet',
- 'photo_patch_position_osp' => 'Foto_Patch-Position_OSP-Seite', 'photo_patch_position_anb' => 'Foto_Patch-Position_ANB-Seite',
- 'measurement_protocol_otdr' => 'ODTR_Messung', 'other' => 'Sonstiges_Dokument'
- ];
-
- $responseDocs = [];
- $typeCounts = [];
-
- foreach ($docs as $doc) {
- $file = new File($doc->fileId);
- $documentTypeKey = $doc->documentType;
- $typeCounts[$documentTypeKey] = ($typeCounts[$documentTypeKey] ?? 0) + 1;
- $originalFilename = $file->orig_filename ?? $file->filename;
- $extension = pathinfo($originalFilename, PATHINFO_EXTENSION);
- $translatedType = $translationMap[$documentTypeKey] ?? $documentTypeKey;
- $newFilename = "{$translatedType}_{$typeCounts[$documentTypeKey]}." . strtolower($extension);
-
- $responseDocs[] = [
- 'id' => $doc->id, 'fileId' => $doc->fileId, 'fileName' => $newFilename, 'description' => $doc->description,
- 'documentType' => $documentTypeKey, 'userName' => UserModel::getOne($doc->createBy)->name ?? 'Unbekannt',
- 'mimetype' => $file->mimetype ?? 'application/octet-stream',
- ];
- }
-
- foreach ($journals as $journal) $journal->createByName = UserModel::getOne($journal->createBy)->name ?? 'Unbekannt';
-
- self::returnJson(['docs' => $responseDocs, 'journals' => $journals]);
- }
-
- private function assignSingleWorkorder($workorderId, $companyId, $deadline, $userId)
- {
- $workorder = RMLWorkorderModel::get($workorderId);
- if (!$workorder) return false;
- $company = RMLWorkorderCompanyModel::get($companyId);
- if (!$company) return false;
-
- $workorder->companyId = $companyId;
- $workorder->status = 'assigned';
- $workorder->assignmentDate = time();
- $workorder->deadlineDate = $deadline;
- RMLWorkorderModel::update((array)$workorder);
-
- RMLWorkorderJournalModel::create([
- 'workorderId' => $workorder->id, 'text' => "Firma '{$company->name}' wurde zugewiesen.",
- 'create' => time(), 'createBy' => $userId,
- ]);
-
- $preorder = new Preorder($workorder->preorderId);
- if ($preorder->id) {
- $preorder->status_id = 10;
- $preorder->edit_by = $this->user->id;
- $preorder->save();
- }
- return true;
- }
-
- protected function assignWorkorderAction()
- {
- $post = json_decode(file_get_contents('php://input'), true);
- if (empty($post['workorderId']) || empty($post['companyId'])) self::sendError("Erforderliche Felder fehlen.");
-
- $deadline = !empty($post['deadlineDate']) ? $post['deadlineDate'] : strtotime('+6 weeks');
- if ($this->assignSingleWorkorder($post['workorderId'], $post['companyId'], $deadline, $this->user->id)) {
- self::returnJson(['success' => true, 'message' => 'Arbeitsauftrag erfolgreich zugewiesen.']);
- } else self::sendError("Arbeitsauftrag konnte nicht zugewiesen werden. Er wurde möglicherweise bereits bearbeitet oder existiert nicht.");
- }
-
- protected function massAssignWorkordersAction()
- {
- $post = json_decode(file_get_contents('php://input'), true);
- if (empty($post['workorderIds']) || empty($post['companyId'])) self::sendError("Erforderliche Felder fehlen.");
-
- $deadline = strtotime($post['deadlineDate'] ?? '+6 weeks');
- $count = 0;
- foreach ($post['workorderIds'] as $workorderId) if ($this->assignSingleWorkorder($workorderId, $post['companyId'], $deadline, $this->user->id)) $count++;
- self::returnJson(['success' => true, 'message' => "$count Arbeitsaufträge erfolgreich zugewiesen."]);
- }
-
- protected function requestCorrectionAction()
- {
- $post = json_decode(file_get_contents('php://input'), true);
- if (empty($post['workorderId']) || empty($post['text'])) self::sendError("Erforderliche Felder fehlen.");
- $workorder = RMLWorkorderModel::get($post['workorderId']);
- if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
-
- $oldStatus = $workorder->status;
- $workorder->status = 'correction_requested';
- RMLWorkorderModel::update((array)$workorder);
-
- RMLWorkorderJournalModel::create([
- 'workorderId' => $workorder->id, 'text' => "Korrektur angefordert. Grund: " . $post['text'],
- 'fileIds' => !empty($post['fileIds']) ? json_encode($post['fileIds']) : null,
- 'statusChange' => $this->getStatusText($oldStatus) . " -> " . $this->getStatusText('correction_requested'),
- 'create' => time(), 'createBy' => $this->user->id,
- ]);
- self::returnJson(['success' => true, 'message' => 'Korrektur wurde angefordert.']);
- }
-
- protected function getCompaniesAction()
- {
- $tenantId = $this->request->tenantId ?? null;
- $companies = RMLWorkorderCompanyModel::getAll([], null, 0, ['key' => 'name', 'order' => 'ASC']);
-
- if ($tenantId) {
- $companies = array_filter($companies, function ($company) use ($tenantId) {
- if ($company->addressId == 4807 && empty($company->visibleForAddressId)) return true;
- $visibleFor = !empty($company->visibleForAddressId) ? json_decode($company->visibleForAddressId, true) : [];
- return in_array($tenantId, $visibleFor);
- });
- }
- self::returnJson(array_values(array_map(fn($c) => ['value' => $c->id, 'text' => $c->name], $companies)));
- }
-
- protected function addJournalAction()
- {
- $post = json_decode(file_get_contents('php://input'), true);
- if (empty($post['workorderId']) || empty(trim($post['text']))) self::sendError("Arbeitsauftrags-ID und Text sind erforderlich.");
-
- RMLWorkorderJournalModel::create(['workorderId' => $post['workorderId'], 'text' => $post['text'], 'createBy' => $this->user->id, 'create' => time()]);
- $journals = array_map(function ($j) {
- $j->createByName = UserModel::getOne($j->createBy)->name ?? 'Unbekannt';
- return (array)$j;
- },
- RMLWorkorderJournalModel::getAll(['workorderId' => $post['workorderId']], null, 0, ['key' => 'create', 'order' => 'DESC'])
- );
- self::returnJson(['success' => true, 'message' => 'Journaleintrag hinzugefügt.', 'journals' => $journals]);
- }
-
- protected function updateDeadlineAction()
- {
- $post = json_decode(file_get_contents('php://input'), true);
- if (empty($post['workorderId']) || empty($post['deadlineDate'])) self::sendError("Erforderliche Felder fehlen.");
-
- $workorder = RMLWorkorderModel::get($post['workorderId']);
- if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
-
- $workorder->deadlineDate = $post['deadlineDate'];
- RMLWorkorderModel::update((array)$workorder);
- RMLWorkorderJournalModel::create(['workorderId' => $workorder->id, 'text' => 'Deadline geändert auf ' . date('d.m.Y', $post['deadlineDate']) . '.', 'create' => time(), 'createBy' => $this->user->id]);
- self::returnJson(['success' => true, 'message' => 'Deadline erfolgreich aktualisiert.']);
- }
-
- protected function acceptDocumentationAction()
- {
- $post = json_decode(file_get_contents('php://input'), true);
- if (empty($post['workorderId'])) self::sendError("Arbeitsauftrags-ID fehlt.");
- $workorder = RMLWorkorderModel::get($post['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.");
-
- $preorder = new Preorder($workorder->preorderId);
- if ($preorder->id) {
- $preorder->status_id = 15;
- $preorder->edit_by = $this->user->id;
- $preorder->save();
- }
-
- $oldStatus = $workorder->status;
- $workorder->status = 'completed';
- RMLWorkorderModel::update((array)$workorder);
- RMLWorkorderJournalModel::create([
- 'workorderId' => $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.']);
- }
-
- protected function setToProblemSolvedAction()
- {
- $post = json_decode(file_get_contents('php://input'), true);
- if (empty($post['workorderId']) || empty($post['text'])) self::sendError("Arbeitsauftrags-ID und Text sind erforderlich.");
-
- $workorder = RMLWorkorderModel::get($post['workorderId']);
- if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
- if ($workorder->status !== 'intervention_required') self::sendError("Der Arbeitsauftrag muss den Status 'Eingriff erforderlich' haben, um als gelöst markiert zu werden.");
-
- $oldStatus = $workorder->status;
- $workorder->status = 'problem_solved';
- RMLWorkorderModel::update((array)$workorder);
- RMLWorkorderJournalModel::create([
- 'workorderId' => $workorder->id, 'text' => "Problem gelöst: " . $post['text'],
- 'statusChange' => $this->getStatusText($oldStatus) . " -> " . $this->getStatusText('problem_solved'),
- 'create' => time(), 'createBy' => $this->user->id,
- ]);
- self::returnJson(['success' => true, 'message' => 'Arbeitsauftrag als "Problem gelöst" markiert.']);
- }
-
- protected function updateAdditionalInfoAction()
- {
- $post = json_decode(file_get_contents('php://input'), true);
- if (empty($post['workorderId'])) self::sendError("Arbeitsauftrags-ID fehlt.");
- $workorder = RMLWorkorderModel::get($post['workorderId']);
- if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
-
- $oldInfo = $workorder->additionalInfo;
- $workorder->additionalInfo = $post['additionalInfo'] ?? null;
- RMLWorkorderModel::update((array)$workorder);
-
- RMLWorkorderJournalModel::create([
- 'workorderId' => $workorder->id, 'text' => "Zusatzinfo geändert.\nAlt: '{$oldInfo}'\nNeu: '{$workorder->additionalInfo}'",
- 'create' => time(), 'createBy' => $this->user->id,
- ]);
- self::returnJson(['success' => true, 'message' => 'Zusatzinfo aktualisiert.']);
- }
-
- protected function cancelWorkorderAction()
- {
- $post = json_decode(file_get_contents('php://input'), true);
- if (empty($post['workorderId'])) self::sendError("Arbeitsauftrags-ID fehlt.");
- $workorder = RMLWorkorderModel::get($post['workorderId']);
- if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
-
- $oldStatus = $workorder->status;
- $workorder->status = 'cancelled';
- RMLWorkorderModel::update((array)$workorder);
-
- RMLWorkorderJournalModel::create([
- 'workorderId' => $workorder->id, 'text' => 'Arbeitsauftrag wurde storniert.' . (!empty($post['reason']) ? ' Grund: ' . $post['reason'] : ''),
- 'statusChange' => $this->getStatusText($oldStatus) . " -> " . $this->getStatusText('cancelled'),
- 'create' => time(), 'createBy' => $this->user->id,
- ]);
-
- $preorder = new Preorder($workorder->preorderId);
- if ($preorder->id) {
- $preorder->status_id = 99;
- $preorder->edit_by = $this->user->id;
- $preorder->save();
- }
-
- self::returnJson(['success' => true, 'message' => 'Arbeitsauftrag wurde storniert.']);
+class RMLWorkorderAdminController extends mfBaseController {
+ protected function init() {
+ $this->needlogin = true;
+ $this->redirect("WorkorderAdmin");
}
}
\ No newline at end of file
diff --git a/application/RMLWorkorderCompany/RMLWorkorderCompanyController.php b/application/RMLWorkorderCompany/RMLWorkorderCompanyController.php
index 90d049526..3c83ff9b2 100644
--- a/application/RMLWorkorderCompany/RMLWorkorderCompanyController.php
+++ b/application/RMLWorkorderCompany/RMLWorkorderCompanyController.php
@@ -1,353 +1,8 @@
'id', 'text' => 'Auftrags-Nr.', 'table' => ['sortable' => true]],
- ['key' => 'preorderInfo', 'text' => 'Kunde', 'modal' => false, 'table' => ['sortable' => false]],
- ['key' => 'rimo_fcp_name', 'text' => 'FCP', 'modal' => false, 'table' => ['sortable' => false]],
- ['key' => 'status', 'text' => 'Status', 'modal' => false, 'table' => ['filter' => 'iconSelect', 'filterOptions' => [
- ['value' => 'new', 'text' => 'Neu', 'icon' => 'fas fa-star text-primary'],
- ['value' => 'assigned', 'text' => 'Zugewiesen', 'icon' => 'fas fa-user-check text-info'],
- ['value' => 'scheduled', 'text' => 'Geplant', 'icon' => 'fas fa-calendar-check text-warning'],
- ['value' => 'correction_requested', 'text' => 'Korrektur angefordert', 'icon' => 'fas fa-exclamation-triangle text-danger'],
- ['value' => 'intervention_required', 'text' => 'Eingriff erforderlich', 'icon' => 'fas fa-times-circle text-danger'],
- ['value' => 'problem_solved', 'text' => 'Problem gelöst', 'icon' => 'fas fa-check-circle text-success'],
- ['value' => 'documented', 'text' => 'Dokumentiert', 'icon' => 'fas fa-file-alt text-success'],
- ['value' => 'completed', 'text' => 'Abgeschlossen', 'icon' => 'fas fa-check-double text-secondary'],
- ['value' => 'cancelled', 'text' => 'Abgebrochen', 'icon' => 'fas fa-ban text-danger'],
- ]]],
- ['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 array $additionalJSVariables = ['COMPANY_ID' => '0'];
-
- private function getStatusText(string $statusKey): string
- {
- foreach ($this->columns as $column) {
- if ($column['key'] === 'status') {
- foreach ($column['table']['filterOptions'] as $option) {
- if ($option['value'] === $statusKey) {
- return $option['text'];
- }
- }
- }
- }
- return ucfirst(str_replace('_', ' ', $statusKey)); // Fallback
- }
-
- protected function prepareCrudConfig()
- {
- $company = RMLWorkorderCompanyModel::getFirst(['addressId' => $this->user->address_id]);
- if ($company) {
- $this->additionalJSVariables['COMPANY_ID'] = $company->id;
- } else {
- $this->additionalJSVariables['COMPANY_ID'] = 0;
- }
- }
-
- protected function indexAction()
- {
- Helper::renderVue($this, 'RMLWorkorderCompany', $this->headerTitle, [
- "CRUD_CONFIG" => $this->getCrudConfig(),
- "TABLE_URL" => $this::getUrl("RMLWorkorderCompany/get"),
- "COMPANY_ID" => $this->additionalJSVariables['COMPANY_ID'],
- ]);
- }
-
- protected function getAction()
- {
- $json = json_decode(file_get_contents('php://input'), true);
- $pagination = $json['pagination'] ?? ['page' => 1, 'per_page' => 10];
- $filters = $json['filters'] ?? [];
- $order = $json['order'] ?? [];
-
- $company = RMLWorkorderCompanyModel::getFirst(['addressId' => $this->user->address_id]);
- if (!$company) {
- self::returnJson(['rows' => [], 'pagination' => array_merge($pagination, ['total_rows' => 0, 'total_pages' => 0, 'filtered_available' => 0])]);
- return;
- }
- $companyId = $company->id;
-
- $workorders = RMLWorkorderModel::getCompanyWorkorders($filters, $pagination['per_page'], ($pagination['page'] - 1) * $pagination['per_page'], $order, $companyId);
- $totalCount = RMLWorkorderModel::countCompanyWorkorders($filters, $companyId);
-
- $rows = array_map(function ($workorder) {
- $row = (array)$workorder;
- $row['preorderInfo'] = $this->getPreorderInfoTextByData($row);
- unset($row['customerName'], $row['customerCompany'], $row['street'], $row['hausnummer'], $row['stiege'], $row['oaid'], $row['apartment'], $row['plz'], $row['city'], $row['phone'], $row['email']);
- return $row;
- }, $workorders);
-
- 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
- ]
- ]);
- }
-
- private function getPreorderInfoTextByData($data)
- {
- $anschlussadresse = "{$data['street']} {$data['hausnummer']}";
- if ($data['stiege']) $anschlussadresse .= "/{$data['stiege']}";
- if ($data['apartment']) $anschlussadresse .= " / WE: {$data['apartment']}";
- $anschlussadresse .= ", {$data['plz']} {$data['city']}";
- $kunde = $data['customerCompany'] ?: $data['customerName'];
- return "Kunde: {$kunde}
" . "Anschluss: {$anschlussadresse}
" . "Kontakt: {$data['phone']} / {$data['email']}
" . "OAID: {$data['oaid']}";
- }
-
- public function getWorkorderByIdAction()
- {
- $id = $this->request->id;
- if (!$id) self::sendError("ID fehlt");
- $workorder = RMLWorkorderModel::get($id);
- if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden");
- $workorder->preorderInfo = $this->getPreorderInfoText($workorder->preorderId);
- self::returnJson((array)$workorder);
- }
-
- private function getPreorderInfoText($preorderId)
- {
- $preorder = new Preorder($preorderId);
- $anschlussadresse = 'N/A';
- if ($preorder->adb_hausnummer_id) {
- $hn = $preorder->adb_hausnummer;
- $anschlussadresse = "{$hn->strasse->name} {$hn->hausnummer}";
- if ($hn->stiege) $anschlussadresse .= "/{$hn->stiege}";
- if ($preorder->adb_wohneinheit_id) $anschlussadresse .= " / WE: {$preorder->adb_wohneinheit->bezeichner}";
- $anschlussadresse .= ", {$hn->plz->plz} {$hn->ortschaft->name}";
- }
- $kunde = ($preorder->company) ?: "{$preorder->firstname} {$preorder->lastname}";
- return "Kunde: {$kunde}
" . "Anschluss: {$anschlussadresse}
" . "Kontakt: {$preorder->phone} / {$preorder->email}
" . "OAID: {$preorder->oaid}";
- }
-
- protected function scheduleAppointmentAction()
- {
- $post = json_decode(file_get_contents('php://input'), true);
- if (empty($post['workorderId']) || empty($post['appointmentDate'])) self::sendError("Erforderliche Felder fehlen.");
- $workorder = RMLWorkorderModel::get($post['workorderId']);
- if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden");
- $hour = (int)date('H', $post['appointmentDate']);
- if ($hour >= 23 || $hour < 1) self::sendError("Bitte geben Sie eine Uhrzeit an!");
-
- $workorder->appointmentDate = $post['appointmentDate'];
- $workorder->status = 'scheduled';
- RMLWorkorderModel::update((array)$workorder);
- RMLWorkorderJournalModel::create([
- 'workorderId' => $workorder->id, 'text' => 'Termin festgelegt auf: ' . date('d.m.Y H:i', $post['appointmentDate']),
- 'create' => time(), 'createBy' => $this->user->id,
- ]);
- self::returnJson(['success' => true, 'message' => 'Termin erfolgreich gespeichert.']);
- }
-
- protected function rescheduleAppointmentAction()
- {
- $post = json_decode(file_get_contents('php://input'), true);
- if (empty($post['workorderId']) || empty($post['appointmentDate']) || empty($post['reason'])) self::sendError("Erforderliche Felder fehlen.");
- $workorder = RMLWorkorderModel::get($post['workorderId']);
- if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
- $hour = (int)date('H', $post['appointmentDate']);
- if ($hour >= 23 || $hour < 1) self::sendError("Bitte geben Sie eine Uhrzeit an!");
-
- $oldDateFormatted = $workorder->appointmentDate ? date('d.m.Y H:i', $workorder->appointmentDate) : 'N/A';
- $newDateFormatted = date('d.m.Y H:i', $post['appointmentDate']);
- $workorder->appointmentDate = $post['appointmentDate'];
- RMLWorkorderModel::update((array)$workorder);
- RMLWorkorderJournalModel::create([
- 'workorderId' => $workorder->id, 'text' => "Termin verschoben von {$oldDateFormatted} auf {$newDateFormatted}. Grund: " . $post['reason'],
- 'create' => time(), 'createBy' => $this->user->id,
- ]);
- self::returnJson(['success' => true, 'message' => 'Termin erfolgreich verschoben.']);
- }
-
- protected function requestInterventionAction()
- {
- $post = json_decode(file_get_contents('php://input'), true);
- if (empty($post['workorderId']) || empty($post['journalText'])) self::sendError("Erforderliche Felder fehlen.");
- $workorder = RMLWorkorderModel::get($post['workorderId']);
- if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
-
- $oldStatus = $workorder->status;
- $workorder->status = 'intervention_required';
- RMLWorkorderModel::update((array)$workorder);
- RMLWorkorderJournalModel::create([
- 'workorderId' => $workorder->id, 'text' => "Eingriff erforderlich: " . $post['journalText'],
- 'statusChange' => $this->getStatusText($oldStatus) . " -> " . $this->getStatusText('intervention_required'),
- 'create' => time(), 'createBy' => $this->user->id,
- ]);
- self::returnJson(['success' => true, 'message' => 'Eingriff wurde angefordert.']);
- }
-
- protected function uploadDocumentationAction()
- {
- if (empty($_FILES['files']) || empty($_POST['workorderId'])) {
- self::returnJson(['error' => 'Erforderliche Daten fehlen.']);
- return;
- }
- $workorderId = $_POST['workorderId'];
- $description = $_POST['description'] ?? '';
- $documentType = $_POST['documentType'] ?? 'general';
- $files = $_FILES['files'];
- $uploadCount = 0;
-
- foreach ($files['name'] as $index => $name) {
- if ($files['error'][$index] === UPLOAD_ERR_OK) {
- $_FILES['file'] = ['name' => $files['name'][$index], 'type' => $files['type'][$index], 'tmp_name' => $files['tmp_name'][$index], 'error' => $files['error'][$index], 'size' => $files['size'][$index]];
- try {
- $uploaded = mfUpload::handleFormUpload("file", false, "/RMLWorkorder");
- RMLWorkorderDocumentationModel::create(['workorderId' => $workorderId, 'fileId' => $uploaded->id, 'description' => $description, 'documentType' => $documentType, 'create' => time(), 'createBy' => $this->user->id]);
- $uploadCount++;
- } catch (Exception $e) {
- error_log("Dateiupload für $name fehlgeschlagen: " . $e->getMessage());
- }
- }
- }
-
- $workorder = RMLWorkorderModel::get($workorderId);
- if ($workorder->status === 'correction_requested' || $workorder->status === 'problem_solved') {
- $workorder->status = 'assigned';
- RMLWorkorderModel::update((array)$workorder);
- $workorder = RMLWorkorderModel::get($workorderId);
- }
- $formattedDocs = $this->getFormattedDocs($workorderId);
- self::returnJson(['success' => true, 'message' => "$uploadCount Datei(en) erfolgreich hochgeladen.", 'docs' => $formattedDocs, 'workorder' => (array)$workorder]);
- }
-
- private function getFormattedDocs($workorderId)
- {
- $docs = RMLWorkorderDocumentationModel::getAll(['workorderId' => $workorderId], null, 0, ['key' => 'create', 'order' => 'ASC']);
- $responseDocs = [];
- $typeCounts = [];
- $translationMap = [
- 'photo_hup_mounted' => 'Foto_montierter_HÜP', 'photo_hup_open' => 'Foto_offener_HÜP',
- 'photo_splice_cassette_hup' => 'Foto_Spleißkassette_HÜP', 'photo_splice_cassette_fcp' => 'Foto_Spleißkassette_FCP',
- 'photo_hup_closed_stickers' => 'Foto_geschlossener_HÜP_mit_Aufklebern', 'photo_fcp_labeled' => 'Foto_FCP_beschriftet',
- 'photo_patch_position_osp' => 'Foto_Patch-Position_OSP-Seite', 'photo_patch_position_anb' => 'Foto_Patch-Position_ANB-Seite',
- 'measurement_protocol_otdr' => 'ODTR_Messung', 'other' => 'Sonstiges_Dokument'
- ];
- foreach ($docs as $doc) {
- $file = new File($doc->fileId);
- $documentTypeKey = $doc->documentType;
- $typeCounts[$documentTypeKey] = ($typeCounts[$documentTypeKey] ?? 0) + 1;
- $originalFilename = $file->orig_filename ?? $file->filename;
- $extension = pathinfo($originalFilename, PATHINFO_EXTENSION);
- $translatedType = $translationMap[$documentTypeKey] ?? $documentTypeKey;
- $newFilename = "{$translatedType}_{$typeCounts[$documentTypeKey]}." . strtolower($extension);
- $responseDocs[] = ['id' => $doc->id, 'fileId' => $doc->fileId, 'fileName' => $newFilename, 'description' => $doc->description, 'documentType' => $documentTypeKey, 'mimetype' => $file->mimetype];
- }
- return $responseDocs;
- }
-
- protected function getDocumentationAction()
- {
- if (empty($this->request->workorderId)) self::sendError("Arbeitsauftrags-ID fehlt.");
- $docs = $this->getFormattedDocs($this->request->workorderId);
- $journals = array_map(function ($j) {
- $j->createByName = UserModel::getOne($j->createBy)->getAbbrName();
- return (array)$j;
- },
- RMLWorkorderJournalModel::getAll(['workorderId' => $this->request->workorderId], null, 0, ['key' => 'create', 'order' => 'DESC'])
- );
- self::returnJson(['docs' => $docs, 'journals' => $journals]);
- }
-
- protected function completeWorkorderAction()
- {
- $post = json_decode(file_get_contents('php://input'), true);
- if (empty($post['workorderId'])) self::sendError("Arbeitsauftrags-ID fehlt.");
- $workorder = RMLWorkorderModel::get($post['workorderId']);
- if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
- $workorder->status = 'documented';
- RMLWorkorderModel::update((array)$workorder);
- self::returnJson(['success' => true, 'message' => 'Arbeitsauftrag abgeschlossen.']);
- }
-
- protected function deleteDocumentationAction()
- {
- $post = json_decode(file_get_contents('php://input'), true);
- if (empty($post['id'])) self::sendError("Dokumenten-ID fehlt.");
- $doc = RMLWorkorderDocumentationModel::get($post['id']);
- if (!$doc) self::sendError("Dokument nicht gefunden.");
- $workorderId = $doc->workorderId;
- RMLWorkorderDocumentationModel::delete($post['id']);
- $formattedDocs = $this->getFormattedDocs($workorderId);
- self::returnJson(['success' => true, 'message' => 'Dokument gelöscht.', 'docs' => $formattedDocs]);
- }
-
- protected function updateDocumentationAction()
- {
- $post = json_decode(file_get_contents('php://input'), true);
- if (empty($post['id'])) self::sendError("Dokumenten-ID fehlt.");
- $doc = RMLWorkorderDocumentationModel::get($post['id']);
- if (!$doc) self::sendError("Dokument nicht gefunden.");
- if (isset($post['documentType'])) $doc->documentType = $post['documentType'];
- RMLWorkorderDocumentationModel::update((array)$doc);
- $formattedDocs = $this->getFormattedDocs($doc->workorderId);
- self::returnJson(['success' => true, 'message' => 'Dokument aktualisiert.', 'docs' => $formattedDocs]);
- }
-
- protected function addJournalAction()
- {
- $post = json_decode(file_get_contents('php://input'), true);
- if (empty($post['workorderId']) || empty(trim($post['text']))) self::sendError("Arbeitsauftrags-ID und Text sind erforderlich.");
- RMLWorkorderJournalModel::create(['workorderId' => $post['workorderId'], 'text' => $post['text'], 'createBy' => $this->user->id, 'create' => time()]);
- $journals = array_map(function ($j) {
- $j->createByName = UserModel::getOne($j->createBy)->getAbbrName();
- return (array)$j;
- },
- RMLWorkorderJournalModel::getAll(['workorderId' => $post['workorderId']], null, 0, ['key' => 'create', 'order' => 'DESC'])
- );
- self::returnJson(['success' => true, 'message' => 'Journaleintrag hinzugefügt.', 'journals' => $journals]);
- }
-
- protected function getTenantConfigAction()
- {
- if (empty($this->request->workorderId)) self::sendError("Arbeitsauftrags-ID fehlt.");
- $workorder = RMLWorkorderModel::get($this->request->workorderId);
- if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
- $preorder = new Preorder($workorder->preorderId);
- if (!$preorder->id) self::sendError("Vorbestellung nicht gefunden.");
- $campaign = new Preordercampaign($preorder->preordercampaign_id);
- if (!$campaign->id) self::sendError("Kampagne nicht gefunden.");
- $network = NetworkModel::getOne($campaign->network_id);
- if (!$network) self::sendError("Netzwerk nicht gefunden.");
- $tenantId = $network->owner_id;
- $tenantConfig = RMLWorkorderTenantConfigModel::getFirst(['addressId' => $tenantId]) ?? RMLWorkorderTenantConfigModel::getFirst(['addressId' => 4807]);
- if (!$tenantConfig) {
- self::returnJson(['success' => false, 'message' => 'Keine Mandantenkonfiguration gefunden.']);
- return;
- }
- self::returnJson(['success' => true, 'documentationTypes' => json_decode($tenantConfig->documentationTypes, true)]);
- }
-
- protected function updateAdditionalInfoAction()
- {
- $post = json_decode(file_get_contents('php://input'), true);
- if (empty($post['workorderId'])) self::sendError("Arbeitsauftrags-ID fehlt.");
-
- $company = RMLWorkorderCompanyModel::getFirst(['addressId' => $this->user->address_id]);
- if (!$company) self::sendError("Firma nicht gefunden.");
-
- $workorder = RMLWorkorderModel::get($post['workorderId']);
- if (!$workorder || $workorder->companyId !== $company->id) self::sendError("Arbeitsauftrag nicht gefunden oder nicht Ihrer Firma zugewiesen.");
-
- $oldInfo = $workorder->additionalInfo;
- $workorder->additionalInfo = $post['additionalInfo'] ?? null;
- RMLWorkorderModel::update((array)$workorder);
-
- RMLWorkorderJournalModel::create([
- 'workorderId' => $workorder->id, 'text' => "Zusatzinfo geändert.\nAlt: '{$oldInfo}'\nNeu: '{$workorder->additionalInfo}'",
- 'create' => time(), 'createBy' => $this->user->id,
- ]);
- self::returnJson(['success' => true, 'message' => 'Zusatzinfo aktualisiert.']);
+class RMLWorkorderCompanyController extends mfBaseController {
+ protected function init() {
+ $this->needlogin = true;
+ $this->redirect("WorkorderCompany");
}
}
\ No newline at end of file
diff --git a/application/RMLWorkorderTenantConfig/RMLWorkorderTenantConfigModel.php b/application/RMLWorkorderTenantConfig/RMLWorkorderTenantConfigModel.php
deleted file mode 100644
index 56a7458d1..000000000
--- a/application/RMLWorkorderTenantConfig/RMLWorkorderTenantConfigModel.php
+++ /dev/null
@@ -1,12 +0,0 @@
- 'id', 'text' => 'Auftrags-Nr.', 'table' => ['sortable' => true, 'filter' => 'numberRange']],
+ ['key' => 'netOwnerId', 'text' => 'Netzeigentümer', 'modal' => false, 'table' => ['filter' => 'select'], 'required' => false],
+ ['key' => 'preorderInfo', 'text' => 'Kunde', 'modal' => false, 'table' => ['filter' => 'search', 'sortable' => false]],
+ ['key' => 'companyName', 'text' => 'Firma', 'modal' => false, 'table' => ['filter' => 'search', 'sortable' => true]],
+ ['key' => 'rimo_fcp_name', 'text' => 'FCP', 'modal' => false, 'table' => ['filter' => 'search', 'sortable' => true]],
+ // Status column is now inherited via prepareCrudConfig
+ ['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]],
+ ];
+
+ /**
+ * Prepares the CRUD configuration.
+ */
+ protected function prepareCrudConfig()
+ {
+ $preorderInfoColIdx = array_search('preorderInfo', array_column($this->columns, 'key'));
+ array_splice($this->columns, $preorderInfoColIdx + 1, 0, [$this->statusColumn]);
+ $netOwnerColIdx = array_search('netOwnerId', array_column($this->columns, 'key'));
+
+ if ($netOwnerColIdx !== false) {
+ if ($this->user->isAdmin()) {
+ $netOwners = Helper::getPreorderCampaignNetworkOwners();
+ $this->columns[$netOwnerColIdx]['table']['filterOptions'] = array_map(fn($o) => ['value' => $o->id, 'text' => $o->company], $netOwners);
+ } else {
+ $this->columns[$netOwnerColIdx]['table'] = false;
+ }
+ }
+ }
+
+ //region ACTIONS
+ public function indexAction()
+ {
+ $this->createWorkordersFromPreorders();
+ parent::indexAction();
+ }
+
+ /**
+ * Fetches workorders for the admin view.
+ */
+ protected function getAction()
+ {
+ $pagination = $this->postData['pagination'] ?? ['page' => 1, 'per_page' => 10];
+ $filters = $this->postData['filters'] ?? [];
+ $order = $this->postData['order'] ?? [];
+
+ $allowedCampaignIds = Helper::getPreorderCampaignFromUser($this->user);
+ if (empty($allowedCampaignIds)) {
+ self::returnJson(['rows' => [], 'pagination' => array_merge($pagination, ['total_rows' => 0, 'total_pages' => 0, 'filtered_available' => 0])]);
+ return;
+ }
+
+ $workorders = WorkorderModel::getAdminWorkorders($filters, $pagination['per_page'], ($pagination['page'] - 1) * $pagination['per_page'], $order, $allowedCampaignIds);
+ $totalCount = WorkorderModel::countAdminWorkorders($filters, $allowedCampaignIds);
+
+ $rows = array_map(function ($workorder) {
+ $row = (array)$workorder;
+ $row['companyName'] ??= 'Nicht zugewiesen';
+ return $row;
+ }, $workorders);
+
+ 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()
+ {
+ $tenantId = $this->request->tenantId;
+ $companies = WorkorderCompanyModel::getAll(['visibleForAddressId' => "%$tenantId%"]);
+ 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');
+
+ if ($this->assignSingleWorkorder($this->postData['workorderId'], $this->postData['companyId'], $deadline, $this->user->id)) {
+ self::returnJson(['success' => true, 'message' => 'Arbeitsauftrag erfolgreich zugewiesen.']);
+ } else {
+ self::sendError("Arbeitsauftrag konnte nicht zugewiesen werden.");
+ }
+ }
+
+ protected function massAssignWorkordersAction()
+ {
+ if (empty($this->postData['workorderIds']) || empty($this->postData['companyId'])) self::sendError("Erforderliche Felder fehlen.");
+
+ $deadline = strtotime($this->postData['deadlineDate'] ?? '+6 weeks');
+ $count = 0;
+ foreach ($this->postData['workorderIds'] as $workorderId) {
+ if ($this->assignSingleWorkorder($workorderId, $this->postData['companyId'], $deadline, $this->user->id)) $count++;
+ }
+ self::returnJson(['success' => true, 'message' => "$count Arbeitsaufträge erfolgreich zugewiesen."]);
+ }
+
+ protected function requestCorrectionAction()
+ {
+ if (empty($this->postData['workorderId']) || empty($this->postData['text'])) self::sendError("Erforderliche Felder fehlen.");
+ $workorder = WorkorderModel::get($this->postData['workorderId']);
+ if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
+
+ $oldStatus = $workorder->status;
+ $workorder->status = 'correction_requested';
+ WorkorderModel::update((array)$workorder);
+
+ WorkorderJournalModel::create([
+ 'workorderId' => $workorder->id, 'text' => "Korrektur angefordert. Grund: " . $this->postData['text'],
+ 'fileIds' => !empty($this->postData['fileIds']) ? json_encode($this->postData['fileIds']) : null,
+ 'statusChange' => $this->getStatusText($oldStatus) . " -> " . $this->getStatusText('correction_requested'),
+ 'create' => time(), 'createBy' => $this->user->id,
+ ]);
+ self::returnJson(['success' => true, 'message' => 'Korrektur wurde angefordert.']);
+ }
+
+ protected function updateDeadlineAction()
+ {
+ if (empty($this->postData['workorderId']) || empty($this->postData['deadlineDate'])) self::sendError("Erforderliche Felder fehlen.");
+
+ $workorder = WorkorderModel::get($this->postData['workorderId']);
+ if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
+
+ $workorder->deadlineDate = $this->postData['deadlineDate'];
+ WorkorderModel::update((array)$workorder);
+ WorkorderJournalModel::create(['workorderId' => $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 = WorkorderModel::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.");
+
+ $preorder = new Preorder($workorder->preorderId);
+ if ($preorder->id) {
+ $preorder->status_id = 15;
+ $preorder->edit_by = $this->user->id;
+ $preorder->save();
+ }
+
+ $oldStatus = $workorder->status;
+ $workorder->status = 'completed';
+ WorkorderModel::update((array)$workorder);
+ WorkorderJournalModel::create([
+ 'workorderId' => $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.']);
+ }
+
+ protected function setToProblemSolvedAction()
+ {
+ if (empty($this->postData['workorderId']) || empty($this->postData['text'])) self::sendError("Arbeitsauftrags-ID und Text sind erforderlich.");
+ $workorder = WorkorderModel::get($this->postData['workorderId']);
+ if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
+ if ($workorder->status !== 'intervention_required') self::sendError("Der Arbeitsauftrag muss den Status 'Eingriff erforderlich' haben.");
+
+ $oldStatus = $workorder->status;
+ $workorder->status = 'problem_solved';
+ WorkorderModel::update((array)$workorder);
+ WorkorderJournalModel::create([
+ 'workorderId' => $workorder->id, 'text' => "Problem gelöst: " . $this->postData['text'],
+ 'statusChange' => $this->getStatusText($oldStatus) . " -> " . $this->getStatusText('problem_solved'),
+ 'create' => time(), 'createBy' => $this->user->id,
+ ]);
+ self::returnJson(['success' => true, 'message' => 'Arbeitsauftrag als "Problem gelöst" markiert.']);
+ }
+
+ protected function cancelWorkorderAction()
+ {
+ if (empty($this->postData['workorderId'])) self::sendError("Arbeitsauftrags-ID fehlt.");
+ $workorder = WorkorderModel::get($this->postData['workorderId']);
+ if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
+
+ $oldStatus = $workorder->status;
+ $workorder->status = 'cancelled';
+ WorkorderModel::update((array)$workorder);
+
+ WorkorderJournalModel::create([
+ 'workorderId' => $workorder->id, 'text' => 'Arbeitsauftrag wurde storniert.' . (!empty($this->postData['reason']) ? ' Grund: ' . $this->postData['reason'] : ''),
+ 'statusChange' => $this->getStatusText($oldStatus) . " -> " . $this->getStatusText('cancelled'),
+ 'create' => time(), 'createBy' => $this->user->id,
+ ]);
+
+ $preorder = new Preorder($workorder->preorderId);
+ if ($preorder->id) {
+ $preorder->status_id = 99; // Storniert
+ $preorder->edit_by = $this->user->id;
+ $preorder->save();
+ }
+ self::returnJson(['success' => true, 'message' => 'Arbeitsauftrag wurde storniert.']);
+ }
+
+ protected function setCivilEngineeringRequiredAction()
+ {
+ if (empty($this->postData['workorderId'])) self::sendError("Arbeitsauftrags-ID fehlt.");
+ if (empty($this->postData['companyId'])) self::sendError("Bitte Tiefbaufirma auswählen.");
+
+ $workorder = WorkorderModel::get($this->postData['workorderId']);
+ if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
+ $company = WorkorderCompanyModel::get($this->postData['companyId']);
+ if (!$company) self::sendError("Tiefbaufirma nicht gefunden.");
+
+ $oldStatus = $workorder->status;
+ $workorder->civilEngineeringCompanyId = $company->id;
+ $workorder->originalCompanyId = $workorder->companyId;
+ $workorder->companyId = $company->id;
+ $workorder->status = 'civil_engineering_required';
+ WorkorderModel::update((array)$workorder);
+
+ WorkorderJournalModel::create([
+ 'workorderId' => $workorder->id, 'text' => "Tiefbau wurde angefordert. Firma '{$company->name}' wurde zugewiesen.",
+ 'statusChange' => $this->getStatusText($oldStatus) . " -> " . $this->getStatusText('civil_engineering_required'),
+ 'create' => time(), 'createBy' => $this->user->id,
+ ]);
+ self::returnJson(['success' => true, 'message' => 'Tiefbau wurde angefordert und Firma zugewiesen.']);
+ }
+
+ //endregion
+
+ //region PRIVATE HELPERS
+ private function assignSingleWorkorder($workorderId, $companyId, $deadline, $userId): bool
+ {
+ $workorder = WorkorderModel::get($workorderId);
+ if (!$workorder) return false;
+ if ($workorder->status === 'civil_engineering_required') {
+ $workorder->companyId = $companyId;
+ $workorder->civilEngineeringCompanyId = $companyId;
+ WorkorderModel::update((array)$workorder);
+ return true;
+ }
+ $company = WorkorderCompanyModel::get($companyId);
+ if (!$company) return false;
+
+ $workorder->companyId = $companyId;
+ $workorder->status = 'assigned';
+ $workorder->assignmentDate = time();
+ $workorder->deadlineDate = $deadline;
+ WorkorderModel::update((array)$workorder);
+
+ WorkorderJournalModel::create([
+ 'workorderId' => $workorder->id, 'text' => "Firma '{$company->name}' wurde zugewiesen.", 'create' => time(), 'createBy' => $userId,
+ ]);
+
+ $preorder = new Preorder($workorder->preorderId);
+ if ($preorder->id) {
+ $preorder->status_id = 10; // In Ausführung
+ $preorder->edit_by = $this->user->id;
+ $preorder->save();
+ }
+ return true;
+ }
+
+ private function createWorkordersFromPreorders()
+ {
+ $configs = WorkorderTenantConfigModel::getAll();
+ foreach ($configs as $config) {
+ $filters = json_decode($config->workorderCreationFilters, true);
+ if (empty($filters)) continue;
+
+ $networks = NetworkModel::getAll(['owner_id' => $config->addressId]);
+ if (empty($networks)) continue;
+
+ $tenantCampaigns = array_map(fn($n) => $n->id, PreordercampaignModel::getAll(['network_id' => array_map(fn($n) => $n->id, $networks)]));
+ if (empty($tenantCampaigns)) continue;
+
+ $filters['preordercampaign_id'] = $tenantCampaigns;
+ $newPreorders = PreorderModel::searchActive($filters);
+
+ foreach ($newPreorders as $preorder) {
+ if (!WorkorderModel::getFirst(['preorderId' => $preorder->id])) {
+ WorkorderModel::create([
+ 'preorderId' => $preorder->id, 'clusterId' => $preorder->preordercampaign_id,
+ 'status' => 'new', 'create' => time(), 'createBy' => 0 // System User
+ ]);
+ }
+ }
+ }
+ }
+ //endregion
+}
\ No newline at end of file
diff --git a/application/WorkorderBase/WorkorderBaseController.php b/application/WorkorderBase/WorkorderBaseController.php
new file mode 100644
index 000000000..12c6c35cb
--- /dev/null
+++ b/application/WorkorderBase/WorkorderBaseController.php
@@ -0,0 +1,139 @@
+ 'status', 'text' => 'Status', 'modal' => false, 'table' => ['filter' => 'iconSelect', 'filterOptions' => [
+ ['value' => 'new', 'text' => 'Neu', 'icon' => 'fas fa-star text-primary'],
+ ['value' => 'assigned', 'text' => 'Zugewiesen', 'icon' => 'fas fa-user-check text-info'],
+ ['value' => 'scheduled', 'text' => 'Geplant', 'icon' => 'fas fa-calendar-check text-warning'],
+ ['value' => 'correction_requested', 'text' => 'Korrektur angefordert', 'icon' => 'fas fa-exclamation-triangle text-danger'],
+ ['value' => 'intervention_required', 'text' => 'Eingriff erforderlich', 'icon' => 'fas fa-times-circle text-danger'],
+ ['value' => 'civil_engineering_required', 'text' => 'Tiefbau benötigt', 'icon' => 'fas fa-hard-hat text-orange'],
+ ['value' => 'civil_engineering_completed', 'text' => 'Tiefbau abgeschlossen', 'icon' => 'fas fa-hard-hat text-success'],
+ ['value' => 'problem_solved', 'text' => 'Problem gelöst', 'icon' => 'fas fa-check-circle text-success'],
+ ['value' => 'documented', 'text' => 'Dokumentiert', 'icon' => 'fas fa-file-alt text-success'],
+ ['value' => 'completed', 'text' => 'Abgeschlossen', 'icon' => 'fas fa-check-double text-secondary'],
+ ['value' => 'cancelled', 'text' => 'Abgebrochen', 'icon' => 'fas fa-ban text-danger'],
+ ]]
+ ];
+
+ protected array $additionalJS = ["js/pages/WorkorderBase/WorkorderBase.js"];
+ protected array $additionalHead = [""];
+
+ /**
+ * Gets the display text for a given status key.
+ * @param string $statusKey
+ * @return string
+ */
+ protected function getStatusText(string $statusKey): string
+ {
+ $statusMap = array_column($this->statusColumn['table']['filterOptions'] ?? [], 'text', 'value');
+ return $statusMap[$statusKey] ?? ucfirst(str_replace('_', ' ', $statusKey));
+ }
+
+ //region SHARED ACTIONS
+ /**
+ * Fetches documentation and journal entries for a given workorder.
+ */
+ protected function getDocumentationAction()
+ {
+ if (empty($this->request->workorderId)) self::sendError("Arbeitsauftrags-ID fehlt.");
+
+ $docs = WorkorderDocumentationModel::getAll(['workorderId' => $this->request->workorderId], null, 0, ['key' => 'create', 'order' => 'ASC']);
+ $journals = WorkorderJournalModel::getAll(['workorderId' => $this->request->workorderId], null, 0, ['key' => 'create', 'order' => 'DESC']);
+
+ $tenantConfig = $this->getTenantConfigFromWorkorder((int)$this->request->workorderId);
+ if ($tenantConfig && !empty($tenantConfig->documentationTypes)) {
+ $customTypes = json_decode($tenantConfig->documentationTypes, true);
+ $customMap = array_column($customTypes, 'text', 'value');
+ $translationMap = array_merge(['civil_engineering_photo' => 'Tiefbau_Foto'], $customMap);
+ }
+
+
+ $responseDocs = [];
+ $typeCounts = [];
+
+ foreach ($docs as $doc) {
+ $file = new File($doc->fileId);
+ $documentTypeKey = $doc->documentType;
+ $typeCounts[$documentTypeKey] = ($typeCounts[$documentTypeKey] ?? 0) + 1;
+ $originalFilename = $file->orig_filename ?? $file->filename;
+ $extension = pathinfo($originalFilename, PATHINFO_EXTENSION);
+ $translatedType = $translationMap[$documentTypeKey] ?? $documentTypeKey;
+ $newFilename = "{$translatedType}_{$typeCounts[$documentTypeKey]}." . strtolower($extension);
+
+ $responseDocs[] = [
+ 'id' => $doc->id, 'fileId' => $doc->fileId, 'fileName' => $newFilename, 'description' => $doc->description,
+ 'documentType' => $documentTypeKey, 'userName' => UserModel::getOne($doc->createBy)->name ?? 'Unbekannt',
+ 'mimetype' => $file->mimetype ?? 'application/octet-stream', 'create' => $doc->create
+ ];
+ }
+
+ foreach ($journals as $journal) {
+ $journal->createByName = UserModel::getOne($journal->createBy)->name ?? 'Unbekannt';
+ }
+
+ self::returnJson(['docs' => $responseDocs, 'journals' => $journals]);
+ }
+
+ /**
+ * Adds a new entry to a workorder's journal.
+ */
+ protected function addJournalAction()
+ {
+ $post = json_decode(file_get_contents('php://input'), true);
+ if (empty($post['workorderId']) || empty(trim($post['text']))) self::sendError("Arbeitsauftrags-ID und Text sind erforderlich.");
+
+ WorkorderJournalModel::create(['workorderId' => $post['workorderId'], 'text' => $post['text'], 'createBy' => $this->user->id, 'create' => time()]);
+
+ $journals = WorkorderJournalModel::getAll(['workorderId' => $post['workorderId']], null, 0, ['key' => 'create', 'order' => 'DESC']);
+ foreach ($journals as $journal) {
+ $journal->createByName = UserModel::getOne($journal->createBy)->name ?? 'Unbekannt';
+ }
+ self::returnJson(['success' => true, 'message' => 'Journaleintrag hinzugefügt.', 'journals' => $journals]);
+ }
+
+ /**
+ * Updates the additional info field for a workorder.
+ */
+ protected function updateAdditionalInfoAction()
+ {
+ $post = json_decode(file_get_contents('php://input'), true);
+ if (empty($post['workorderId'])) self::sendError("Arbeitsauftrags-ID fehlt.");
+ $workorder = WorkorderModel::get($post['workorderId']);
+ if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
+
+ $oldInfo = $workorder->additionalInfo;
+ $newInfo = $post['additionalInfo'] ?? null;
+ $workorder->additionalInfo = $newInfo;
+ WorkorderModel::update((array)$workorder);
+
+ WorkorderJournalModel::create([
+ 'workorderId' => $workorder->id, 'text' => "Zusatzinfo geändert.\nAlt: '{$oldInfo}'\nNeu: '{$newInfo}'",
+ 'create' => time(), 'createBy' => $this->user->id,
+ ]);
+ self::returnJson(['success' => true, 'message' => 'Zusatzinfo aktualisiert.', 'newInfo' => $newInfo]);
+ }
+ //endregion
+
+ protected function getTenantConfigFromWorkorder(int $workorderId) {
+ if (empty($workorderId)) self::sendError("Arbeitsauftrags-ID fehlt.");
+ $workorder = WorkorderModel::get($workorderId);
+ if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
+ $preorder = new Preorder($workorder->preorderId);
+ if (!$preorder->id) self::sendError("Vorbestellung nicht gefunden.");
+ $campaign = new Preordercampaign($preorder->preordercampaign_id);
+ if (!$campaign->id) self::sendError("Kampagne nicht gefunden.");
+ $network = NetworkModel::getOne($campaign->network_id);
+ if (!$network) self::sendError("Netzwerk nicht gefunden.");
+
+ return WorkorderTenantConfigModel::getFirst(['addressId' => $network->owner_id]) ?? null;
+ }
+}
\ No newline at end of file
diff --git a/application/WorkorderCompany/WorkorderCompanyController.php b/application/WorkorderCompany/WorkorderCompanyController.php
new file mode 100644
index 000000000..38273e7e9
--- /dev/null
+++ b/application/WorkorderCompany/WorkorderCompanyController.php
@@ -0,0 +1,195 @@
+ 'id', 'text' => 'Auftrags-Nr.', 'table' => ['sortable' => true]],
+ ['key' => 'networkOwnerName', 'text' => 'Auftraggeber', 'table' => ['sortable' => false]],
+ ['key' => 'preorderInfo', 'text' => 'Kunde', 'modal' => false, 'table' => ['sortable' => false]],
+ ['key' => 'rimo_fcp_name', 'text' => 'FCP', 'modal' => false, 'table' => ['sortable' => false]],
+ // Status column is now inherited via prepareCrudConfig
+ ['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 array $additionalJSVariables = ['COMPANY_ID' => '0'];
+
+ protected function prepareCrudConfig() {
+ $preorderInfoColIdx = array_search('preorderInfo', array_column($this->columns, 'key'));
+ array_splice($this->columns, $preorderInfoColIdx + 1, 0, [$this->statusColumn]);
+ $company = WorkorderCompanyModel::getFirst(['addressId' => $this->user->address_id]);
+ $this->additionalJSVariables['COMPANY_ID'] = $company ? $company->id : 0;
+ }
+
+ //region ACTIONS
+ public function indexAction() {
+ parent::indexAction();
+ }
+
+ protected function getAction() {
+ $pagination = $this->postData['pagination'] ?? ['page' => 1, 'per_page' => 10];
+ $filters = $this->postData['filters'] ?? [];
+ $order = $this->postData['order'] ?? [];
+
+ $company = WorkorderCompanyModel::getFirst(['addressId' => $this->user->address_id]);
+ if (!$company) {
+ self::returnJson(['rows' => [], 'pagination' => array_merge($pagination, ['total_rows' => 0, 'total_pages' => 0, 'filtered_available' => 0])]);
+ return;
+ }
+
+ $workorders = WorkorderModel::getCompanyWorkorders($filters, $pagination['per_page'], ($pagination['page'] - 1) * $pagination['per_page'], $order, $company->id);
+ $totalCount = WorkorderModel::countCompanyWorkorders($filters, $company->id);
+
+ self::returnJson([
+ 'rows' => $workorders,
+ 'pagination' => ['page' => $pagination['page'], 'per_page' => $pagination['per_page'], 'total_rows' => $totalCount, 'total_pages' => ceil($totalCount / $pagination['per_page']), 'filtered_available' => $totalCount]
+ ]);
+ }
+
+ public function getWorkorderByIdAction() {
+ if (empty($this->request->id)) self::sendError("ID fehlt");
+ $workorder = WorkorderModel::get($this->request->id);
+ if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden");
+ self::returnJson((array)$workorder);
+ }
+
+ protected function scheduleAppointmentAction() {
+ if (empty($this->postData['workorderId']) || empty($this->postData['appointmentDate'])) self::sendError("Erforderliche Felder fehlen.");
+ $workorder = WorkorderModel::get($this->postData['workorderId']);
+ if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden");
+ if ((int)date('H', $this->postData['appointmentDate']) >= 23 || (int)date('H', $this->postData['appointmentDate']) < 1) self::sendError("Bitte geben Sie eine Uhrzeit an!");
+
+ $workorder->appointmentDate = $this->postData['appointmentDate'];
+ $workorder->status = 'scheduled';
+ WorkorderModel::update((array)$workorder);
+
+ WorkorderJournalModel::create([
+ 'workorderId' => $workorder->id, 'text' => 'Termin festgelegt auf: ' . date('d.m.Y H:i', $this->postData['appointmentDate']),
+ 'create' => time(), 'createBy' => $this->user->id,
+ ]);
+ self::returnJson(['success' => true, 'message' => 'Termin erfolgreich gespeichert.']);
+ }
+
+ protected function rescheduleAppointmentAction() {
+ if (empty($this->postData['workorderId']) || empty($this->postData['appointmentDate']) || empty($this->postData['reason'])) self::sendError("Erforderliche Felder fehlen.");
+ $workorder = WorkorderModel::get($this->postData['workorderId']);
+ if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
+ if ((int)date('H', $this->postData['appointmentDate']) >= 23 || (int)date('H', $this->postData['appointmentDate']) < 1) self::sendError("Bitte geben Sie eine Uhrzeit an!");
+
+ $oldDateFormatted = $workorder->appointmentDate ? date('d.m.Y H:i', $workorder->appointmentDate) : 'N/A';
+ $newDateFormatted = date('d.m.Y H:i', $this->postData['appointmentDate']);
+ $workorder->appointmentDate = $this->postData['appointmentDate'];
+ WorkorderModel::update((array)$workorder);
+
+ WorkorderJournalModel::create([
+ 'workorderId' => $workorder->id, 'text' => "Termin verschoben von {$oldDateFormatted} auf {$newDateFormatted}. Grund: " . $this->postData['reason'],
+ 'create' => time(), 'createBy' => $this->user->id,
+ ]);
+ self::returnJson(['success' => true, 'message' => 'Termin erfolgreich verschoben.']);
+ }
+
+ protected function requestInterventionAction() {
+ if (empty($this->postData['workorderId']) || empty($this->postData['journalText'])) self::sendError("Erforderliche Felder fehlen.");
+ $workorder = WorkorderModel::get($this->postData['workorderId']);
+ if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
+
+ $oldStatus = $workorder->status;
+ $workorder->status = 'intervention_required';
+ WorkorderModel::update((array)$workorder);
+
+ WorkorderJournalModel::create([
+ 'workorderId' => $workorder->id, 'text' => "Eingriff erforderlich: " . $this->postData['journalText'],
+ 'statusChange' => $this->getStatusText($oldStatus) . " -> " . $this->getStatusText('intervention_required'),
+ 'create' => time(), 'createBy' => $this->user->id,
+ ]);
+ self::returnJson(['success' => true, 'message' => 'Eingriff wurde angefordert.']);
+ }
+
+ protected function completeWorkorderAction() {
+ if (empty($this->postData['workorderId'])) self::sendError("Arbeitsauftrags-ID fehlt.");
+ $workorder = WorkorderModel::get($this->postData['workorderId']);
+ if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
+
+ $workorder->status = 'documented';
+ WorkorderModel::update((array)$workorder);
+ self::returnJson(['success' => true, 'message' => 'Arbeitsauftrag zur Prüfung eingereicht.']);
+ }
+
+ protected function getTenantConfigAction() {
+ $tenantConfig = $this->getTenantConfigFromWorkorder($this->request->workorderId);
+
+ if (!$tenantConfig) {
+ self::returnJson(['success' => false, 'message' => 'Keine Mandantenkonfiguration gefunden.']);
+ return;
+ }
+ self::returnJson(['success' => true, 'documentationTypes' => json_decode($tenantConfig->documentationTypes, true), 'civilEngineeringDocsRequired' => $tenantConfig->civilEngineeringDocsRequired]);
+ }
+
+ protected function uploadDocumentationAction() {
+ if (empty($_FILES['files']) || empty($_POST['workorderId'])) self::sendError('Erforderliche Daten fehlen.');
+ $workorderId = $_POST['workorderId'];
+
+ foreach ($_FILES['files']['name'] as $index => $name) {
+ if ($_FILES['files']['error'][$index] === UPLOAD_ERR_OK) {
+ $_FILES['file'] = ['name' => $name, 'type' => $_FILES['files']['type'][$index], 'tmp_name' => $_FILES['files']['tmp_name'][$index], 'error' => $_FILES['files']['error'][$index], 'size' => $_FILES['files']['size'][$index]];
+ try {
+ $uploaded = mfUpload::handleFormUpload("file", false, "/Workorder");
+ WorkorderDocumentationModel::create(['workorderId' => $workorderId, 'fileId' => $uploaded->id, 'description' => $_POST['description'] ?? '', 'documentType' => $_POST['documentType'] ?? 'general', 'create' => time(), 'createBy' => $this->user->id]);
+ } catch (Exception $e) { /* Log error if necessary */
+ }
+ }
+ }
+
+ $workorder = WorkorderModel::get($workorderId);
+ if (in_array($workorder->status, ['correction_requested', 'problem_solved', 'civil_engineering_completed'])) {
+ $workorder->status = 'assigned';
+ WorkorderModel::update((array)$workorder);
+ }
+
+ self::returnJson(['success' => true, 'message' => "Datei(en) erfolgreich hochgeladen."]);
+ }
+
+ protected function deleteDocumentationAction() {
+ if (empty($this->postData['id'])) self::sendError("Dokumenten-ID fehlt.");
+ WorkorderDocumentationModel::delete($this->postData['id']);
+ self::returnJson(['success' => true, 'message' => 'Dokument gelöscht.']);
+ }
+
+ protected function updateDocumentationAction() {
+ if (empty($this->postData['id'])) self::sendError("Dokumenten-ID fehlt.");
+ $doc = WorkorderDocumentationModel::get($this->postData['id']);
+ if (!$doc) self::sendError("Dokument nicht gefunden.");
+ if (isset($this->postData['documentType'])) $doc->documentType = $this->postData['documentType'];
+ WorkorderDocumentationModel::update((array)$doc);
+ self::returnJson(['success' => true, 'message' => 'Dokument aktualisiert.']);
+ }
+
+ protected function completeCivilEngineeringAction() {
+ if (empty($this->postData['workorderId'])) self::sendError("Arbeitsauftrags-ID fehlt.");
+ $workorder = WorkorderModel::get($this->postData['workorderId']);
+ if (!$workorder) self::sendError("Arbeitsauftrag nicht gefunden.");
+
+ // Re-assign to original company
+ if ($workorder->originalCompanyId) {
+ $workorder->companyId = $workorder->originalCompanyId;
+ $workorder->originalCompanyId = null;
+ }
+
+ $oldStatus = $workorder->status;
+ $workorder->civilEngineeringCompanyId = null;
+ $workorder->status = 'civil_engineering_completed';
+ WorkorderModel::update((array)$workorder);
+
+ WorkorderJournalModel::create([
+ 'workorderId' => $workorder->id, 'text' => "Tiefbau abgeschlossen.",
+ 'statusChange' => $this->getStatusText($oldStatus) . " -> " . $this->getStatusText('civil_engineering_completed'),
+ 'create' => time(), 'createBy' => $this->user->id,
+ ]);
+ self::returnJson(['success' => true, 'message' => 'Tiefbau erfolgreich abgeschlossen.']);
+ }
+ //endregion
+}
\ No newline at end of file
diff --git a/application/RMLWorkorderCompany/RMLWorkorderCompanyModel.php b/application/WorkorderCompany/WorkorderCompanyModel.php
similarity index 65%
rename from application/RMLWorkorderCompany/RMLWorkorderCompanyModel.php
rename to application/WorkorderCompany/WorkorderCompanyModel.php
index 318d44d77..b24193820 100644
--- a/application/RMLWorkorderCompany/RMLWorkorderCompanyModel.php
+++ b/application/WorkorderCompany/WorkorderCompanyModel.php
@@ -1,7 +1,7 @@
preorderId)) return null;
+
+ $db = self::getDB();
+ $dbName = FRONKDB_DBNAME;
+ $tableWTC = self::getFullyQualifiedTable();
+ $preorderId = $db->real_escape_string($workorder->preorderId);
+
+ $sql = "SELECT wtc.* FROM $tableWTC wtc
+ JOIN `$dbName`.`Network` n ON wtc.addressId = n.owner_id
+ JOIN `$dbName`.`Preordercampaign` pc ON n.id = pc.network_id
+ JOIN `$dbName`.`Preorder` p ON pc.id = p.preordercampaign_id
+ WHERE p.id = '$preorderId' LIMIT 1";
+
+ $row = $db->query($sql)?->fetch_assoc();
+
+ return $row ? new self($row) : null;
+ }}
\ No newline at end of file
diff --git a/db/migrations/20250901410000_workorder_rename.php b/db/migrations/20250901410000_workorder_rename.php
new file mode 100644
index 000000000..339448e8e
--- /dev/null
+++ b/db/migrations/20250901410000_workorder_rename.php
@@ -0,0 +1,81 @@
+table('RMLWorkorder')->rename('Workorder');
+ $this->table('RMLWorkorderCompany')->rename('WorkorderCompany');
+ $this->table('RMLWorkorderDocumentation')->rename('WorkorderDocumentation');
+ $this->table('RMLWorkorderJournal')->rename('WorkorderJournal');
+ $this->table('RMLWorkorderTenantConfig')->rename('WorkorderTenantConfig');
+
+ $workorderTable = $this->table('Workorder');
+ $workorderTable->addColumn('civilEngineeringCompanyId', 'integer', ['null' => true, 'default' => null, 'after' => 'companyId', 'comment' => 'Company assigned for civil engineering task'])
+ ->addColumn('originalCompanyId', 'integer', ['null' => true, 'default' => null, 'after' => 'civilEngineeringCompanyId', 'comment' => 'Stores the companyId before assigning to civil engineering'])
+ ->addIndex(['civilEngineeringCompanyId'], ['name' => 'civilEngineeringCompanyId_idx'])
+ ->addIndex(['originalCompanyId'], ['name' => 'originalCompanyId_idx'])
+ ->save();
+
+ $workorderTable->changeColumn('status', 'enum', [
+ 'values' => [
+ 'new',
+ 'assigned',
+ 'scheduled',
+ 'correction_requested',
+ 'intervention_required',
+ 'civil_engineering_required',
+ 'civil_engineering_completed',
+ 'problem_solved',
+ 'documented',
+ 'completed',
+ 'cancelled'
+ ],
+ 'default' => 'new',
+ 'null' => false
+ ])->save();
+
+ $tenantConfigTable = $this->table('WorkorderTenantConfig');
+ $tenantConfigTable->addColumn('civilEngineeringDocsRequired', 'boolean', ['default' => false, 'null' => false, 'after' => 'workorderCreationFilters', 'comment' => 'If true, civil engineering company must upload docs to complete task'])
+ ->save();
+ }
+
+ public function down(): void
+ {
+ $tenantConfigTable = $this->table('WorkorderTenantConfig');
+ $tenantConfigTable->removeColumn('civilEngineeringDocsRequired')->save();
+
+ $workorderTable = $this->table('Workorder');
+ $workorderTable->changeColumn('status', 'enum', [
+ 'values' => [
+ 'new',
+ 'assigned',
+ 'scheduled',
+ 'correction_requested',
+ 'intervention_required',
+ 'problem_solved',
+ 'documented',
+ 'completed',
+ 'cancelled'
+ ],
+ 'default' => 'new',
+ 'null' => false
+ ])->save();
+
+ $workorderTable->removeColumn('civilEngineeringCompanyId')
+ ->removeColumn('originalCompanyId')
+ ->removeIndexByName('civilEngineeringCompanyId_idx')
+ ->removeIndexByName('originalCompanyId_idx')
+ ->save();
+
+ $this->table('Workorder')->rename('RMLWorkorder');
+ $this->table('WorkorderCompany')->rename('RMLWorkorderCompany');
+ $this->table('WorkorderDocumentation')->rename('RMLWorkorderDocumentation');
+ $this->table('WorkorderJournal')->rename('RMLWorkorderJournal');
+ $this->table('WorkorderTenantConfig')->rename('RMLWorkorderTenantConfig');
+ }
+}
diff --git a/lib/Helper/Helper.php b/lib/Helper/Helper.php
index e81644fc7..cf013a7ce 100644
--- a/lib/Helper/Helper.php
+++ b/lib/Helper/Helper.php
@@ -213,4 +213,16 @@ class Helper {
return $returnObject ? $campaigns : array_column($campaigns, 'id');
}
+
+ public static function getPreorderCampaignNetworkOwners() {
+ $sql = "SELECT a.id FROM Preordercampaign pc
+ LEFT JOIN Network n ON pc.network_id = n.id
+ LEFT JOIN Address a ON n.owner_id = a.id
+ GROUP BY a.id
+ ORDER BY a.company, a.lastname, a.firstname";
+
+ $results = FronkDB::singleton()->fetch_all_assoc(FronkDB::singleton()->query($sql)) ?? [];
+
+ return array_map(fn($owner) => new Address($owner['id']), $results);
+ }
}
\ No newline at end of file
diff --git a/public/js/pages/RMLWorkorderAdmin/RMLWorkorderAdmin.js b/public/js/pages/RMLWorkorderAdmin/RMLWorkorderAdmin.js
deleted file mode 100644
index 06963ffb9..000000000
--- a/public/js/pages/RMLWorkorderAdmin/RMLWorkorderAdmin.js
+++ /dev/null
@@ -1,601 +0,0 @@
-// RMLWorkorderAdmin.js
-Vue.component('r-m-l-workorder-admin', {
- template: `
-
Wählen Sie die zu korrigierenden Dokumente aus der Galerie aus und geben Sie - einen Grund an.
-Prüfen Sie, ob alle erforderlichen Dokumente vorhanden und korrekt sind.
-Auftrag: #{{ rescheduleData.workorder.id }}
-Falls ein Problem auftritt, das ein Eingreifen erfordert, melden Sie es - hier.
-{{ stats.assigned || 0 }}
-{{ stats.urgent || 0 }}
-{{ stats.scheduled || 0 }}
-{{ stats.new || 0 }}
-{{ stats.in_progress || 0 }}
-{{ stats.overdue || 0 }}
-{{ stats.completed_30d || 0 }}
-Auftrag: #{{ civilEngineeringData.workorder.id }}
+Soll der Auftrag #{{ cancelWorkorderModalData.id }} wirklich storniert werden?
+Soll das Problem bei Auftrag #{{ problemSolvedModalData.id }} als gelöst markiert werden?
+Sollen {{ workordersToAssign.length }} Workorder(s) der Firma {{ massAssignModalData.companyName }} zugewiesen werden?
+Schließen Sie den Tiefbau-Auftrag ab. Laden Sie Dokumente hoch, falls erforderlich.
+Prüfen Sie, ob alle erforderlichen Dokumente vorhanden und korrekt sind.
+Die ausgewählten Dokumente werden als fehlerhaft markiert. Bitte geben Sie einen Grund an.
+Falls ein Problem auftritt, das ein Eingreifen erfordert, melden Sie es hier.
+Auftrag: #{{ rescheduleModalData.workorder.id }}
+