Files
thetool/public/js/pages/Radius/RadiusOntParser.js
2025-12-09 05:34:24 +00:00

186 lines
7.9 KiB
JavaScript

/* ===== RadiusOntParser.js (Vue 3 + TT-Core) ===== */
const RadiusOntParser = {
name: 'RadiusOntParser',
template: `
<div class="tt-scope ont-card">
<div v-if="step===1">
<div class="block-head">
<div class="h4"><i class="fa-duotone fa-file-spreadsheet"></i> Schritt 1 · Excel (XLSX) Upload</div>
<div class="muted small">Laden Sie eine XLSX-Datei mit Ihren Kundendaten.</div>
</div>
<tt-file-dropzone accept=".xlsx,.xls" @file-selected="readXlsx" />
</div>
<div v-if="step===2">
<div class="block-head">
<div class="h4"><i class="fa-duotone fa-sliders"></i> Schritt 2 · Spaltenzuordnung</div>
</div>
<div class="grid g-4 cols-2 cols-1@sm">
<div class="field" v-for="field in requiredFields" :key="field.key">
<label>{{ field.label }}</label>
<div class="select">
<select v-model="selectedColumns[field.key]">
<option v-for="h in headers" :key="h" :value="h">{{ h }}</option>
</select>
</div>
</div>
</div>
<div class="cluster mt-3">
<button class="primary-btn" @click="startProcessing">
<i class="fa-duotone fa-play"></i> Verarbeitung starten
</button>
<button class="ghost-btn" @click="step = 1">
<i class="fa-duotone fa-arrow-left"></i> Zurück
</button>
</div>
</div>
<div v-if="step===3">
<div class="block-head">
<div class="h4"><i class="fa-duotone fa-list-check"></i> Schritt 3 · Ergebnisse</div>
<div class="cluster">
<button class="primary-btn" @click="downloadResults" :disabled="loading">
<i class="fa-duotone fa-download"></i> Neue Excel herunterladen
</button>
<button class="ghost-btn" @click="step = 2" :disabled="loading">
<i class="fa-duotone fa-arrow-left"></i> Zurück
</button>
<button class="ghost-btn" @click="resetLocal" :disabled="loading">
<i class="fa-duotone fa-rotate-right"></i> Neue Verarbeitung
</button>
</div>
</div>
<div class="results-container mt-between">
<tt-loading-indicator
v-if="loading"
:text="'Aktueller Kunde: ' + (currentCustomerNumber || '—')"
:progress="progress"
style="min-height: 200px;"
/>
<tt-data-table
v-else
:items="processedData"
:has-searched="true"
no-results-placeholder-text="Keine Daten verarbeitet."
>
<template #head>
<thead>
<tr>
<th v-for="h in requiredFields" :key="h.key">{{ h.label }}</th>
<th>ONT SN</th>
</tr>
</thead>
</template>
<template #row="{ item }">
<td>{{ item[selectedColumns.kundennummer] }}</td>
<td>{{ item[selectedColumns.anschlussstrasse] }}</td>
<td>{{ item[selectedColumns.anschlussplz] }}</td>
<td>{{ item[selectedColumns.anschlusscity] }}</td>
<td class="mono">{{ item.ont_sn }}</td>
</template>
</tt-data-table>
<div v-if="!loading && processedData.length" class="results-summary">
{{ processedData.length }} Zeilen verarbeitet
</div>
</div>
</div>
</div>
`,
data: () => ({
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,
currentCustomerNumber: ''
}),
methods: {
async readXlsx(file) {
await window.TT_CORE.loadScript('https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.17.0/xlsx.full.min.js');
const fr = new FileReader();
fr.onload = (e) => {
const wb = XLSX.read(new Uint8Array(e.target.result), { type: 'array' });
this.parsedData = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
this.headers = Object.keys(this.parsedData[0] || {});
this.step = 2;
};
fr.readAsArrayBuffer(file);
},
async startProcessing() {
this.step = 3;
this.loading = true;
this.totalRows = this.parsedData.length;
this.processedData = [];
this.currentRow = 0;
const p = [];
const b = window.TT_CONFIG.BASE_PATH;
loop: for (let i = 0; i < this.parsedData.length; i++) {
this.currentRow = i;
this.progress = ((i + 1) / this.totalRows) * 100;
const row = { ...this.parsedData[i] };
this.currentCustomerNumber = row[this.selectedColumns.kundennummer] || '';
try {
const { data: users } = await axios.get(`${b}/Radius/proxyUnsecureHTTPRequestToRadius`, {
params: { custnume: row[this.selectedColumns.kundennummer] }
});
if (users.length === 0) {
row.ont_sn = 'N/A - Kein Benutzer';
} else if (users.length === 1) {
const { data: d } = await axios.get(`${b}/Radius/proxyUnsecureHTTPRequestToRadius`, {
params: { skipAdditional: 'true', action2: 'fetchRadacct', username: users[0].username }
});
row.ont_sn = d.ont_sn || 'N/A - Keine ONT SN';
} else {
const [s, pl, c] = [row[this.selectedColumns.anschlussstrasse], row[this.selectedColumns.anschlussplz], row[this.selectedColumns.anschlusscity]];
for (let u of users) {
if (window.TT_CORE.validateData(s, pl, c, u.info || users[0].info || '')) {
const { data: d } = await axios.get(`${b}/Radius/proxyUnsecureHTTPRequestToRadius`, {
params: { skipAdditional: 'true', action2: 'fetchRadacct', username: u.username }
});
row.ont_sn = d.ont_sn || 'N/A - Keine ONT SN';
p.push(row);
continue loop;
}
}
row.ont_sn = 'N/A - Anschluss nicht zugeordnet';
}
} catch {
row.ont_sn = 'N/A - Fehler';
}
p.push(row);
if ((i + 1) % 20 === 0) await new Promise(r => setTimeout(r, 20));
}
this.loading = false;
this.processedData = p;
},
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');
},
resetLocal() {
Object.assign(this.$data, this.$options.data.call(this));
}
}
};
// Register component with Vue 3 app
if (window.VueApp) {
window.VueApp.component('radius-ont-parser', RadiusOntParser);
}