diff --git a/application/Radius/RadiusController.php b/application/Radius/RadiusController.php
index 782d60660..7e129785d 100644
--- a/application/Radius/RadiusController.php
+++ b/application/Radius/RadiusController.php
@@ -15,7 +15,7 @@ class RadiusController extends mfBaseController {
protected function indexAction() {
$this->layout()->set('additionalJS', ["plugins/chart.js/chart.4.4.6.js", "plugins/chart.js/chartjs-adapter-moment.min.js"]);
- Helper::renderVue($this, $this->mod, "Radius", []);
+ Helper::renderVue($this, $this->mod, "Radius", ['CAN_BILLING' => $this->me->can("Billing")]);
}
diff --git a/public/js/pages/Radius/Radius.css b/public/js/pages/Radius/Radius.css
index d219763fd..66f1a5ee1 100644
--- a/public/js/pages/Radius/Radius.css
+++ b/public/js/pages/Radius/Radius.css
@@ -6,6 +6,11 @@
margin-bottom: 20px;
}
+.radius-view-selector > *,
+.radius-view-selector > * > * {
+ width: 100%;
+}
+
@media (max-width: 576px) {
.radius-view-selector {
grid-template-columns: 1fr;
@@ -53,3 +58,24 @@
padding: 15px;
border-radius: 5px;
}
+
+
+
+/*
+RADIUS ONT PARSER
+*/
+.loading-overlay {
+ position: relative;
+ padding: 20px;
+ background: white;
+ border-radius: 4px;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+}
+
+.progress {
+ height: 30px;
+}
+
+.progress-bar {
+ transition: width 0.3s ease;
+}
\ No newline at end of file
diff --git a/public/js/pages/Radius/Radius.js b/public/js/pages/Radius/Radius.js
index dd5d56ec9..182f7a37b 100644
--- a/public/js/pages/Radius/Radius.js
+++ b/public/js/pages/Radius/Radius.js
@@ -1,3 +1,279 @@
+// Function to calculate similarity percentage between two strings
+function calculateSimilarity(str1, str2) {
+ // Normalize strings by converting them to lowercase
+ str1 = str1.toLowerCase();
+ str2 = str2.toLowerCase();
+
+ let matchCount = 0;
+
+ // Check how many characters in str1 exist in str2
+ for (let char of str1) {
+ if (str2.includes(char)) {
+ matchCount++;
+ }
+ }
+
+ // Calculate similarity percentage
+ return (matchCount / str1.length) * 100;
+}
+
+function validateData(strasse, plz, stadt, info) {
+ const thresholds = 90; // Similarity threshold in percentage
+
+ // Validate each field against the info string
+ return !(calculateSimilarity(strasse, info) < thresholds ||
+ calculateSimilarity(plz, info) < thresholds ||
+ calculateSimilarity(stadt, info) < thresholds);
+}
+
+
+Vue.component('radius-ont-parser', {
+ template: `
+
+
+
+
+
Schritt 1: Excel (XLSX) Upload
+
+
+
+
+
+
+
+
Schritt 2: Spaltenzuordnung
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Schritt 3: Ergebnisse
+
+
+
+
+
+
+ | {{ header.label }} |
+
+ ONT SN |
+
+
+
+
+ | {{ row[selectedColumns.kundennummer] }} |
+ {{ row[selectedColumns.anschlussstrasse] }} |
+ {{ row[selectedColumns.anschlussplz] }} |
+ {{ row[selectedColumns.anschlusscity] }} |
+ {{ row.ont_sn }} |
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ Math.round(progress) }}%
+
+
+
Processing {{ currentRow + 1 }} of {{ totalRows }}
+
+
+ `,
+
+ data() {
+ return {
+ step: 1,
+ headers: [],
+ parsedData: [],
+ processedData: [],
+ selectedColumns: {
+ kundennummer: 'crmPartner',
+ anschlussstrasse: 'AnlStrasse',
+ anschlussplz: 'AnlPlz',
+ anschlusscity: 'AnlOrt'
+ },
+ requiredFields: [
+ { key: 'kundennummer', label: 'Kundennummer' },
+ { key: 'anschlussstrasse', label: 'Anschlussstraße' },
+ { key: 'anschlussplz', label: 'Anschluss PLZ' },
+ { key: 'anschlusscity', label: 'Anschluss City' }
+ ],
+ loading: false,
+ progress: 0,
+ currentRow: 0,
+ totalRows: 0
+ };
+ },
+
+ methods: {
+ async handleFileUpload(event) {
+ const file = event.target.files[0];
+ if (!file) return;
+
+ // Load XLSX library dynamically
+ await this.loadXLSX();
+
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ const data = new Uint8Array(e.target.result);
+ const workbook = XLSX.read(data, { type: "array" });
+ const worksheet = workbook.Sheets[workbook.SheetNames[0]];
+
+ // Read entire sheet as rows of arrays (no header detection yet)
+ const allRows = XLSX.utils.sheet_to_json(worksheet, {
+ header: 1, // Return rows as arrays
+ blankrows: false // Skip blank rows
+ });
+
+ // If there's only one row or something unexpected, do a basic parse
+ if (allRows.length < 2) {
+ const fallbackData = XLSX.utils.sheet_to_json(worksheet);
+ this.parsedData = fallbackData;
+ this.headers = Object.keys(fallbackData[0] || {});
+ this.step = 2;
+ return;
+ }
+
+ const firstRow = allRows[0] || [];
+ const secondRow = allRows[1] || [];
+
+ // Count how many cells in each row are empty
+ const firstRowEmptyCount = firstRow.length - firstRow.filter(Boolean).length;
+ const secondRowEmptyCount = secondRow.length - secondRow.filter(Boolean).length;
+
+ // If the difference in empty cells is more than 25% of the number of columns in the second row,
+ // assume the first row is mostly empty and use the second row as the header
+ const useSecondRowAsHeader = (firstRowEmptyCount - secondRowEmptyCount) > 0.25 * secondRow.length;
+
+ // Now parse again with the correct header row
+ if (useSecondRowAsHeader) {
+ this.parsedData = XLSX.utils.sheet_to_json(worksheet, {
+ range: 1, // Start reading data after the first row
+ header: secondRow, // Use the second row as the header
+ defval: "" // Optional: fill empty cells with empty string
+ }).slice(1); // Skip the first row
+ this.headers = secondRow;
+ } else {
+ this.parsedData = XLSX.utils.sheet_to_json(worksheet, {
+ range: 0, // Start at the first row
+ header: firstRow, // Use the first row as the header
+ defval: ""
+ });
+ this.headers = firstRow;
+ }
+
+ this.step = 2;
+ };
+ reader.readAsArrayBuffer(file);
+ },
+
+
+
+ async loadXLSX() {
+ if (!window.XLSX) {
+ await new Promise((resolve, reject) => {
+ const script = document.createElement('script');
+ script.src = 'https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.17.0/xlsx.full.min.js';
+ script.onload = resolve;
+ script.onerror = reject;
+ document.head.appendChild(script);
+ });
+ }
+ },
+
+ async startProcessing() {
+ this.loading = true;
+ this.totalRows = this.parsedData.length;
+ const processedRows = [];
+
+ mainLoop:
+ for (let i = 0; i < this.parsedData.length; i++) {
+ this.currentRow = i;
+ this.progress = ((i + 1) / this.parsedData.length) * 100;
+
+ // Simulate processing
+ await this.sleep(100);
+
+ // Process row here
+ const row = this.parsedData[i];
+
+ const findUserResponse = await fetch(window.TT_CONFIG['BASE_PATH'] + '/Radius/proxyUnsecureHTTPRequestToRadius?custnume=' + row[this.selectedColumns.kundennummer]);
+ const findUserData = await findUserResponse.json();
+
+ if (findUserData.length === 0) {
+ row.ont_sn = 'N/A - Kein Benutzer mit dieser Kundennummer gefunden';
+ processedRows.push(row);
+ } else if (findUserData.length === 1) {
+ const username = findUserData[0].username;
+ const radacctResponse = await fetch(window.TT_CONFIG['BASE_PATH'] + '/Radius/proxyUnsecureHTTPRequestToRadius?skipAdditional=true&action2=fetchRadacct&username=' + username);
+ const radacctData = await radacctResponse.json();
+
+ row.ont_sn = radacctData.ont_sn || 'N/A - Keine ONT SN gefunden';
+ processedRows.push(row);
+
+ } else if (findUserData.length > 1) {
+ // check string simulairty of strasse, plz, stadt and atleast of 90% of each should be inside findUserData[].info
+ // if not, ont_sn = N/A - Anschluss konnte nicht zugeordnet werden
+
+ const strasse = row[this.selectedColumns.anschlussstrasse];
+ const plz = row[this.selectedColumns.anschlussplz];
+ const stadt = row[this.selectedColumns.anschlusscity];
+ const info = findUserData[0].info;
+
+ for (let user of findUserData) {
+ if (validateData(strasse, plz, stadt, info)) {
+ const username = user.username;
+ const radacctResponse = await fetch(window.TT_CONFIG['BASE_PATH'] + '/Radius/proxyUnsecureHTTPRequestToRadius?skipAdditional=true&action2=fetchRadacct&username=' + username);
+ const radacctData = await radacctResponse.json();
+
+ row.ont_sn = radacctData.ont_sn || 'N/A - Keine ONT SN gefunden';
+ processedRows.push(row);
+ continue mainLoop;
+ }
+ }
+
+ row.ont_sn = 'N/A - Anschluss konnte nicht zugeordnet werden';
+ processedRows.push(row);
+ }
+ }
+
+ this.loading = false;
+ this.processedData = processedRows;
+ this.step = 3;
+ },
+ downloadResults() {
+ const ws = XLSX.utils.json_to_sheet(this.processedData);
+ const wb = XLSX.utils.book_new();
+ XLSX.utils.book_append_sheet(wb, ws, "Results");
+ XLSX.writeFile(wb, "results.xlsx");
+ },
+ sleep(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+ }
+ }
+});
+
+
Vue.component('radius', {
template: `
@@ -6,18 +282,9 @@ Vue.component('radius', {
-
-
+
+
+
@@ -94,6 +361,10 @@ Vue.component('radius', {
+
+
+
+
@@ -156,13 +427,12 @@ Vue.component('radius', {
},
methods: {
hideRadacctModal() {
- console.log('hideRadacctModal');
this.showRadacctModal = false;
},
async loadRadiusUsers() {
let custnum = '';
if (this.$refs.billAddr.displayValue.length > 5) {
- custnum = this.$refs.billAddr.displayValue.match(/\[(\d+)\]/)[1];
+ custnum = this.$refs.billAddr.displayValue.match(/\[(\d+)]/)[1];
}
const params = new URLSearchParams({