571 lines
27 KiB
JavaScript
571 lines
27 KiB
JavaScript
Vue.component('tt-chip', {
|
|
props: {
|
|
checked: { type: Boolean, default: false }
|
|
},
|
|
template: `<div class="tt-chip" :class="{ 'is-checked': checked }"><slot></slot></div>`
|
|
});
|
|
|
|
Vue.component('Cpeprovisioning', {
|
|
template: `
|
|
<div class="cpe-provisioning-page">
|
|
<div class="filter-wrapper">
|
|
<div class="filter-grid">
|
|
<tt-select label="Netzgebiet" :options="networkOptions" v-model="filters.network_id" @input="fetchData(true)" sm/>
|
|
<tt-select label="Provisioningstatus" :options="statusOptions" v-model="filters.routerconfig_finished" @input="fetchData(true)" sm/>
|
|
<tt-select label="Verzögerte Herstellung" :options="delayOptions" v-model="filters.hide_delayed_finish" @input="fetchData(true)" sm/>
|
|
<tt-input label="Suche" v-model="filters.owner" sm placeholder="Kunde, SPIN, Adresse..." @input="handleSearchInput" @keyup.native.enter="fetchData(true)"/>
|
|
<div class="filter-actions">
|
|
<tt-button text="Anwenden" @click="fetchData(true)" additional-class="btn-primary" sm/>
|
|
<tt-button text="Zurücksetzen" @click="resetFilters" additional-class="btn-secondary" sm/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="loading && items.length === 0" class="loading-indicator">
|
|
<div class="spinner-border text-primary" role="status"><span class="sr-only">Loading...</span></div>
|
|
<p class="mt-2">Daten werden geladen...</p>
|
|
</div>
|
|
|
|
<div v-if="!loading && filteredItems.length === 0" class="no-results-indicator">
|
|
<i class="fas fa-info-circle fa-2x text-muted mb-2"></i>
|
|
<p>Keine Einträge für die aktuellen Filter gefunden.</p>
|
|
</div>
|
|
|
|
<div class="cpe-cards-container">
|
|
<div v-for="item in filteredItems" :key="item.orderproduct_id" class="cpe-card" :class="{ 'is-dirty': item.isDirty }">
|
|
<div class="cpe-card-header">
|
|
<div class="customer-info">
|
|
<div>
|
|
<span class="name">{{ item.customer }}</span>
|
|
<span v-if="item.owner_customer_number" class="badge badge-light text-muted">#{{ item.owner_customer_number }}</span>
|
|
</div>
|
|
<small v-if="item.spin" class="meta">SPIN: <span class="text-primary">{{ item.spin }}</span></small>
|
|
</div>
|
|
|
|
<div class="location-info">
|
|
<div><i class="fas fa-network-wired"></i> {{ item.network || 'N/A' }}</div>
|
|
<div><i class="fas fa-map-marker-alt"></i> {{ item.owner_full_address || 'N/A' }}</div>
|
|
<div v-if="item.owner_phone"><i class="fas fa-phone"></i> {{ item.owner_phone }}</div>
|
|
<div v-if="item.owner_email"><i class="fas fa-envelope"></i> {{ item.owner_email }}</div>
|
|
</div>
|
|
|
|
<div class="header-actions">
|
|
<a target="_blank" :href="window.TT_CONFIG.ORDER_URL + '/Index/?id=' + item.order_id + '&addJournal=1'">
|
|
<tt-tooltip text="Bestelljournal" position="top"><i class="fas fa-scroll"></i></tt-tooltip>
|
|
</a>
|
|
<a target="_blank" :href="window.TT_CONFIG.CPE_PROV_PRINT_PDF_URL + '?order_id=' + item.order_id">
|
|
<tt-tooltip text="Label drucken" position="top"><i class="fas fa-print"></i></tt-tooltip>
|
|
</a>
|
|
<a target="_blank" :href="getRadiusSearchUrl(item)">
|
|
<tt-tooltip text="Radius User suchen" position="top"><i class="fas fa-wifi"></i></tt-tooltip>
|
|
</a>
|
|
<tt-tooltip v-if="item.vot" text="Vorortinstallation" position="top"><i class="fas fa-tools text-purple"></i></tt-tooltip>
|
|
<tt-tooltip v-if="item.hw" :text="item.hw" position="top"><i class="fas fa-shopping-bag text-purple"></i></tt-tooltip>
|
|
<tt-tooltip v-if="item.voip" text="Voice Produkt vorhanden" position="top"><i class="fas fa-phone text-purple"></i></tt-tooltip>
|
|
<tt-tooltip v-if="item.note" :text="item.note" position="top" allow-wrapping><i class="fas fa-clipboard-list text-purple"></i></tt-tooltip>
|
|
<a target="_blank" :href="item.snopp_url" v-if="item.show_snopp_button">
|
|
<tt-tooltip text="SNOPP" position="top" allow-wrapping>
|
|
<img style="height: 18px;" src="/img/snop-logo.png" alt="Snop Logo">
|
|
</tt-tooltip>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="cpe-card-content">
|
|
<!-- Column 1: Configuration -->
|
|
<div class="content-column">
|
|
<h5>Router Konfiguration</h5>
|
|
<tt-select label="Router Modell" :options="routerOptions" v-model="item.cpe_data.routertype"
|
|
@input="markDirty(item); checkShipping(item); handleRouterTypeChange(item)" sm no-form-group/>
|
|
|
|
<div class="d-flex align-items-end" style="gap: 5px;">
|
|
<tt-input label="Router MAC (Scan)" v-model="item.cpe_data.mac"
|
|
@input="val => handleMacInput(item, val)" sm no-form-group style="flex-grow: 1;"/>
|
|
<button @click="copyToClipboard(item.cpe_data.mac)" :disabled="!item.cpe_data.mac"
|
|
class="btn btn-sm btn-light border" style="height: 31px;" title="MAC kopieren">
|
|
<i class="fas fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<tt-input label="WLAN SSID" v-model="item.cpe_data.wifi_ssid" @input="markDirty(item)" sm no-form-group/>
|
|
<tt-input label="WPA Key" v-model="item.cpe_data.wifi_pass" @input="markDirty(item)" sm no-form-group/>
|
|
|
|
<tt-input v-if="item.termination_id" label="ONT Seriennummer" v-model="item.ont_sn"
|
|
@input="markDirty(item)" sm no-form-group
|
|
:additional-props="{ placeholder: item.ont_deployed ? 'ONT montiert' : 'ONT nicht montiert' }"/>
|
|
</div>
|
|
|
|
<!-- Column 2: Shipping -->
|
|
<div class="content-column">
|
|
<h5>Versand & Logistik</h5>
|
|
<div class="d-flex align-items-center justify-content-between mb-2">
|
|
<label class="mb-0">Versandauftrag</label>
|
|
<tt-switch v-model="item.cpe_data.shipping" @input="markDirty(item);checkShipping(item)"/>
|
|
</div>
|
|
<div class="shipping-dims">
|
|
<tt-input label="Gewicht (kg)" v-model="item.cpe_data.ship_weight" @input="markDirty(item)" sm type="number" :disabled="!item.cpe_data.shipping"/>
|
|
<tt-input label="Länge (cm)" v-model="item.cpe_data.ship_length" @input="markDirty(item)" sm type="number" :disabled="!item.cpe_data.shipping"/>
|
|
<tt-input label="Breite (cm)" v-model="item.cpe_data.ship_width" @input="markDirty(item)" sm type="number" :disabled="!item.cpe_data.shipping"/>
|
|
<tt-input label="Höhe (cm)" v-model="item.cpe_data.ship_height" @input="markDirty(item)" sm type="number" :disabled="!item.cpe_data.shipping"/>
|
|
</div>
|
|
<tt-textarea label="Interner Kommentar" v-model="item.cpe_data.note" @input="markDirty(item)" sm no-form-group rows="2"/>
|
|
</div>
|
|
|
|
<!-- Column 3: Product Info -->
|
|
<div class="content-column">
|
|
<h5>Produkt & Services</h5>
|
|
<div class="product-info">
|
|
<span class="product-name">{{ item.product_name }}</span>
|
|
<div class="product-badges">
|
|
<span class="badge badge-secondary">{{ item.product_code }}</span>
|
|
<span class="badge badge-info">{{ item.access_type }}</span>
|
|
</div>
|
|
<div class="mt-1 text-muted">
|
|
<i class="fas fa-arrow-down"></i> {{ item.access_type_down }} | <i class="fas fa-arrow-up"></i> {{ item.access_type_up }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="vlans-container">
|
|
<template v-for="(vlan, key) in item.vlans">
|
|
<label v-if="vlan.tag" class="vlan-chip" :key="key">
|
|
<input type="checkbox" :checked="vlan.checked" @change="markDirty(item); vlan.checked = !vlan.checked"/>
|
|
<span>{{ key.charAt(0).toUpperCase() + key.slice(1) }}: {{ vlan.tag }}</span>
|
|
</label>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Column 4: Actions -->
|
|
<div class="content-column action-column">
|
|
<h5>Aktionen</h5>
|
|
<div>
|
|
<tt-button text="Radius User anlegen"
|
|
@click="createRadiusUser(item)"
|
|
:disabled="!isValidMac(item.cpe_data.mac)"
|
|
sm block
|
|
additional-class="btn-outline-primary mb-2"
|
|
icon="fas fa-user-plus"
|
|
title="An Chrome Extension senden" />
|
|
</div>
|
|
|
|
<div class="mt-auto">
|
|
<div class="finish-toggle">
|
|
<label><strong>Konfiguration fertig</strong></label>
|
|
<tt-switch v-model="item.cpe_data.routerconfig_finished" @input="markDirty(item)"/>
|
|
</div>
|
|
<tt-button text="Speichern" @click="saveCpe(item)"
|
|
:loading="item.isSaving" :disabled="!item.isDirty"
|
|
:additional-class="item.isDirty ? 'btn-success' : 'btn-secondary'"
|
|
block icon="fas fa-save"/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="loading && items.length > 0" class="text-center mt-4 mb-4">
|
|
<div class="spinner-border text-primary" role="status"><span class="sr-only">Loading...</span></div>
|
|
</div>
|
|
|
|
<div class="text-center mt-4 mb-4" v-if="!loading && pagination.total_pages && page <= pagination.total_pages">
|
|
<tt-button text="Weitere laden" @click="fetchData(false)" additional-class="btn-primary" />
|
|
</div>
|
|
|
|
<!-- Custom Extension ID Modal -->
|
|
<div class="custom-modal-overlay" v-if="showExtensionIdModal" @click.self="showExtensionIdModal=false">
|
|
<div class="custom-modal-container">
|
|
<div class="custom-modal-header">
|
|
<h3>Extension ID Konfigurieren</h3>
|
|
<button class="custom-modal-close" @click="showExtensionIdModal=false">×</button>
|
|
</div>
|
|
<div class="custom-modal-body">
|
|
<div class="form-group">
|
|
<label>Chrome Extension ID</label>
|
|
<div class="input-group">
|
|
<div class="input-group-prepend">
|
|
<span class="input-group-text"><i class="fas fa-puzzle-piece"></i></span>
|
|
</div>
|
|
<input type="text" class="form-control" v-model.trim="extensionId" placeholder="z.B. jglijfiddilckddlmbnlojmmlahboffh">
|
|
</div>
|
|
<small class="form-text text-muted">Die ID der 'TheTool Helper' Chrome Extension.</small>
|
|
</div>
|
|
</div>
|
|
<div class="custom-modal-footer">
|
|
<button class="btn btn-secondary mr-2" @click="showExtensionIdModal=false">Abbrechen</button>
|
|
<button class="btn btn-primary" @click="saveExtensionId">Speichern</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
`,
|
|
data() {
|
|
return {
|
|
window,
|
|
loading: true,
|
|
items: [],
|
|
filteredItems: [],
|
|
filters: {
|
|
network_id: '',
|
|
routerconfig_finished: '0',
|
|
hide_delayed_finish: '1',
|
|
owner: ''
|
|
},
|
|
statusOptions: [ { value: '0', text: 'Offen' }, { value: '1', text: 'Abgeschlossen' } ],
|
|
delayOptions: [ { value: '1', text: 'Nicht anzeigen' }, { value: '0', text: 'Anzeigen' } ],
|
|
page: 1,
|
|
pagination: {},
|
|
searchDebounceTimer: null,
|
|
macInputTimers: {},
|
|
extensionId: 'jglijfiddilckddlmbnlojmmlahboffh',
|
|
showExtensionIdModal: false,
|
|
processingMacItems: new Set()
|
|
}
|
|
},
|
|
computed: {
|
|
networkOptions() {
|
|
const networks = window.TT_CONFIG.NETWORKS || [];
|
|
return [{ value: '', text: 'Alle Gebiete' }, ...networks.map(net => ({ value: net.id, text: net.name }))];
|
|
},
|
|
routerOptions() {
|
|
return window.TT_CONFIG.ROUTER_OPTIONS || [];
|
|
}
|
|
},
|
|
created() {
|
|
// Removed usage of _.debounce
|
|
// Load Extension ID from local storage
|
|
const savedExtensionId = localStorage.getItem('radiusExtensionId');
|
|
if (savedExtensionId) {
|
|
this.extensionId = savedExtensionId;
|
|
}
|
|
window.addEventListener('keydown', this.handleKeydown);
|
|
},
|
|
beforeDestroy() {
|
|
window.removeEventListener('keydown', this.handleKeydown);
|
|
Object.keys(this.macInputTimers).forEach(key => {
|
|
clearTimeout(this.macInputTimers[key]);
|
|
});
|
|
if (this.searchDebounceTimer) {
|
|
clearTimeout(this.searchDebounceTimer);
|
|
}
|
|
},
|
|
methods: {
|
|
copyToClipboard(text) {
|
|
if(!text) return;
|
|
navigator.clipboard.writeText(text)
|
|
.then(() => window.notify('success', 'Kopiert!'))
|
|
.catch(() => window.notify('error', 'Fehler beim Kopieren'));
|
|
},
|
|
getRadiusSearchUrl(item) {
|
|
const custNum = item.owner_customer_number || '';
|
|
const basePath = window.TT_CONFIG.BASE_PATH || '';
|
|
// If customer number starts with 7000, use ESTMK search mode
|
|
if (custNum.startsWith('7000')) {
|
|
return `${basePath}/Radius?estmk_nr=${encodeURIComponent(custNum)}`;
|
|
}
|
|
// Otherwise use autocomplete search with custnum
|
|
return `${basePath}/Radius?custnum=${encodeURIComponent(custNum)}`;
|
|
},
|
|
handleKeydown(e) {
|
|
// CTRL + ALT + E to open Extension Config
|
|
if (e.code === 'KeyE' && e.ctrlKey && e.altKey) {
|
|
e.preventDefault();
|
|
this.openExtensionIdModal();
|
|
}
|
|
},
|
|
openExtensionIdModal() {
|
|
this.showExtensionIdModal = true;
|
|
},
|
|
saveExtensionId() {
|
|
if(this.extensionId) {
|
|
localStorage.setItem('radiusExtensionId', this.extensionId);
|
|
this.showExtensionIdModal = false;
|
|
window.notify('success', 'Extension ID gespeichert.');
|
|
} else {
|
|
window.notify('error', 'Bitte eine ID eingeben.');
|
|
}
|
|
},
|
|
isValidMac(mac) {
|
|
if (!mac) return false;
|
|
const macRegex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
|
|
return macRegex.test(mac);
|
|
},
|
|
handleSearchInput() {
|
|
if (this.searchDebounceTimer) clearTimeout(this.searchDebounceTimer);
|
|
this.searchDebounceTimer = setTimeout(() => {
|
|
this.fetchData(true);
|
|
}, 400);
|
|
},
|
|
async fetchData(isNewSearch = false) {
|
|
if (isNewSearch) {
|
|
this.page = 1;
|
|
this.items = [];
|
|
this.filteredItems = [];
|
|
this.pagination = {};
|
|
}
|
|
|
|
if (!isNewSearch && this.pagination.total_pages && this.page > this.pagination.total_pages) {
|
|
return;
|
|
}
|
|
|
|
this.loading = true;
|
|
|
|
const payload = {
|
|
pagination: { page: this.page, per_page: 25 },
|
|
filters: { ...this.filters },
|
|
order: { key: 'order_id', order: 'desc' }
|
|
};
|
|
|
|
try {
|
|
const { data } = await axios.post(window.TT_CONFIG.CPE_PROV_API_GET_URL, payload);
|
|
const newItems = (data.rows || []).map(item => ({
|
|
...item,
|
|
isDirty: false,
|
|
isSaving: false,
|
|
pop_name: item.pop_name || 'N/A',
|
|
owner_address: `${item.owner_street || ''} ${item.owner_housenumber || ''}, ${item.owner_zip || ''} ${item.owner_city || ''}`,
|
|
owner_phone: item.owner_phone || '',
|
|
owner_email: item.owner_email || '',
|
|
}));
|
|
|
|
if (isNewSearch) {
|
|
this.items = newItems;
|
|
} else {
|
|
this.items.push(...newItems);
|
|
}
|
|
|
|
this.pagination = data.pagination;
|
|
this.page++;
|
|
this.filteredItems = this.items;
|
|
|
|
} catch (error) {
|
|
console.error("Error fetching CPE data:", error);
|
|
window.notify('error', 'Fehler beim Laden der Daten.');
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
resetFilters() {
|
|
this.filters = { network_id: '', routerconfig_finished: '0', hide_delayed_finish: '1', owner: '' };
|
|
this.fetchData(true);
|
|
},
|
|
markDirty(item) {
|
|
this.$set(item, 'isDirty', true);
|
|
},
|
|
|
|
// --- MAC & QR Logic ---
|
|
|
|
parseMacFromQrCode(qrCode) {
|
|
if (!qrCode) return null;
|
|
// Remove whitespace and newlines
|
|
const cleaned = qrCode.replace(/[\s\n\r]+/g, '');
|
|
|
|
// Strict Pattern: Must contain a dash separating 6 hex and 12 hex
|
|
// Example: "CWMP-ID=00040E-802395709D7C" or just "00040E-802395709D7C"
|
|
const matchDash = cleaned.match(/([0-9A-Fa-f]{6})-([0-9A-Fa-f]{12})/);
|
|
|
|
if (matchDash) {
|
|
console.log('[MAC Parser] Found Pattern:', matchDash[0]);
|
|
// RETURN ONLY THE PART AFTER THE DASH (Group 2)
|
|
return matchDash[2];
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
calculateMacOffset(macAddress, offset) {
|
|
// Convert to BigInt to handle 48-bit integer math safely
|
|
const macDecimal = BigInt('0x' + macAddress);
|
|
const newMacDecimal = macDecimal + BigInt(offset);
|
|
let newMacHex = newMacDecimal.toString(16).toUpperCase();
|
|
// Ensure 12 chars padding
|
|
return newMacHex.padStart(12, '0');
|
|
},
|
|
|
|
formatMacAddress(macAddress) {
|
|
// Strip existing delimiters
|
|
const cleaned = macAddress.replace(/[:-]/g, '');
|
|
// Add colons
|
|
return cleaned.match(/.{1,2}/g).join(':').toUpperCase();
|
|
},
|
|
|
|
handleMacInput(item, val) {
|
|
if (val !== undefined) {
|
|
// Fix encoding issue: replace ß (scharfes s) with - before processing
|
|
item.cpe_data.mac = val.replace(/ß/g, '-');
|
|
}
|
|
|
|
const itemKey = item.orderproduct_id;
|
|
this.markDirty(item);
|
|
|
|
// Clear existing timer
|
|
if (this.macInputTimers[itemKey]) {
|
|
clearTimeout(this.macInputTimers[itemKey]);
|
|
}
|
|
|
|
// Check if input matches QR code pattern (XXXXXX-XXXXXXXXXXXX)
|
|
const qrPattern = /[0-9A-Fa-f]{6}-[0-9A-Fa-f]{12}/;
|
|
const hasCompleteQr = qrPattern.test(item.cpe_data.mac || '');
|
|
|
|
if (hasCompleteQr) {
|
|
// Complete QR code detected - short delay to ensure full scan is received
|
|
this.macInputTimers[itemKey] = setTimeout(() => {
|
|
this.processMacAddress(item);
|
|
}, 150);
|
|
} else {
|
|
// Manual entry or incomplete scan - longer debounce
|
|
this.macInputTimers[itemKey] = setTimeout(() => {
|
|
this.processMacAddress(item);
|
|
}, 600);
|
|
}
|
|
},
|
|
|
|
handleRouterTypeChange(item) {
|
|
// Re-process MAC if router type changes (offset might change)
|
|
if (item.cpe_data.mac) {
|
|
this.processMacAddress(item);
|
|
}
|
|
},
|
|
|
|
processMacAddress(item) {
|
|
let inputValue = item.cpe_data.mac;
|
|
const routerType = item.cpe_data.routertype;
|
|
|
|
if (!inputValue) return;
|
|
|
|
// Fix encoding issue: replace ß (scharfes s) with - before processing
|
|
if (inputValue.includes('ß')) {
|
|
inputValue = inputValue.replace(/ß/g, '-');
|
|
this.$set(item.cpe_data, 'mac', inputValue); // Update field with corrected value
|
|
}
|
|
|
|
// Only process QR codes for FritzBox 4050 and 7690
|
|
if (routerType === 'FritzBox 4050' || routerType === 'FritzBox 7690') {
|
|
const parsedMac = this.parseMacFromQrCode(inputValue);
|
|
|
|
if (parsedMac) {
|
|
// QR code pattern found, calculate offset and format
|
|
let offset = 0;
|
|
if (routerType === 'FritzBox 4050') offset = -3;
|
|
else if (routerType === 'FritzBox 7690') offset = 2;
|
|
|
|
try {
|
|
const newMac = (offset !== 0) ? this.calculateMacOffset(parsedMac, offset) : parsedMac;
|
|
const formatted = this.formatMacAddress(newMac);
|
|
|
|
if (item.cpe_data.mac !== formatted) {
|
|
this.$set(item.cpe_data, 'mac', formatted);
|
|
const offsetStr = offset > 0 ? `+${offset}` : `${offset}`;
|
|
window.notify('success', `MAC berechnet (${offsetStr}): ${formatted}`);
|
|
}
|
|
} catch (e) {
|
|
console.error('MAC Calculation error', e);
|
|
window.notify('error', 'Fehler bei MAC Berechnung');
|
|
}
|
|
return; // Exit after QR processing
|
|
}
|
|
}
|
|
|
|
// For all router types (including 4050/7690 without QR): format manual entry
|
|
const cleanInput = inputValue.replace(/[:-]/g, '').replace(/\s/g, '');
|
|
if (cleanInput.length === 12 && /^[0-9A-Fa-f]{12}$/.test(cleanInput)) {
|
|
const formatted = this.formatMacAddress(cleanInput);
|
|
if (item.cpe_data.mac !== formatted) {
|
|
this.$set(item.cpe_data, 'mac', formatted);
|
|
}
|
|
}
|
|
},
|
|
|
|
checkShipping(item) {
|
|
if (item.cpe_data.shipping && item.cpe_data.routertype) {
|
|
const shippingData = this.window.TT_CONFIG.ROUTER_SHIPPING_DATA ? this.window.TT_CONFIG.ROUTER_SHIPPING_DATA[item.cpe_data.routertype] : null;
|
|
if (shippingData) {
|
|
item.cpe_data.ship_weight = shippingData.weight;
|
|
item.cpe_data.ship_length = shippingData.length;
|
|
item.cpe_data.ship_width = shippingData.width;
|
|
item.cpe_data.ship_height = shippingData.height;
|
|
item.cpe_data = { ...item.cpe_data }; // Trigger reactivity
|
|
this.window.notify('success', 'Versanddaten übernommen.');
|
|
}
|
|
}
|
|
},
|
|
|
|
createRadiusUser(item) {
|
|
window.notify('info', 'Sende Daten an Chrome Extension...');
|
|
|
|
const message = {
|
|
type: "CREATE_RADIUS_USER",
|
|
payload: {
|
|
customerNumber: item.owner_customer_number || 'N/A',
|
|
macAddress: item.cpe_data.mac,
|
|
address: item.owner_full_address || 'N/A',
|
|
customerName: item.customer || 'N/A',
|
|
productName: item.product_name || 'N/A'
|
|
}
|
|
};
|
|
|
|
if (window.chrome && chrome.runtime && chrome.runtime.sendMessage) {
|
|
try {
|
|
chrome.runtime.sendMessage(this.extensionId, message, (response) => {
|
|
if (chrome.runtime.lastError) {
|
|
console.warn(chrome.runtime.lastError.message);
|
|
window.notify('warning', 'Kommunikation fehlgeschlagen. Extension installiert?');
|
|
} else {
|
|
window.notify('success', 'Daten gesendet!');
|
|
}
|
|
});
|
|
} catch (e) {
|
|
window.notify('error', 'Fehler: ' + e.message);
|
|
}
|
|
} else {
|
|
window.notify('warning', 'Chrome Messaging API nicht verfügbar.');
|
|
}
|
|
},
|
|
|
|
_buildSavePayload(item) {
|
|
return {
|
|
id: item.cpe_id,
|
|
order_id: item.order_id,
|
|
orderproduct_id: item.orderproduct_id,
|
|
termination_id: item.termination_id,
|
|
ont_sn: item.ont_sn,
|
|
vlans: item.vlans,
|
|
...item.cpe_data,
|
|
routertype: item.cpe_data.routertype || '', // Ensure empty string instead of null
|
|
shipping: item.cpe_data.shipping ? 1 : 0,
|
|
routerconfig_finished: item.cpe_data.routerconfig_finished ? 1 : 0,
|
|
};
|
|
},
|
|
|
|
async saveCpe(item) {
|
|
this.$set(item, 'isSaving', true);
|
|
const payload = this._buildSavePayload(item);
|
|
|
|
try {
|
|
const { data } = await axios.post(this.window.TT_CONFIG.CPE_PROV_API_SAVE_URL, payload);
|
|
if (data.success) {
|
|
this.window.notify('success', data.message);
|
|
if (this.filters.routerconfig_finished === '0' && payload.routerconfig_finished) {
|
|
this.items = this.items.filter(i => i.orderproduct_id !== item.orderproduct_id);
|
|
this.filteredItems = this.items;
|
|
} else {
|
|
const index = this.items.findIndex(i => i.orderproduct_id === item.orderproduct_id);
|
|
if (index !== -1) {
|
|
this.$set(this.items[index], 'isDirty', false);
|
|
}
|
|
}
|
|
} else {
|
|
this.window.notify('error', data.message || 'Fehler beim Speichern.');
|
|
}
|
|
} catch (error) {
|
|
this.window.notify('error', 'Ein unerwarteter Fehler ist aufgetreten.');
|
|
} finally {
|
|
this.$set(item, 'isSaving', false);
|
|
}
|
|
}
|
|
},
|
|
mounted() {
|
|
this.fetchData(true);
|
|
}
|
|
});
|