From fc7b70b30704a16a19759fb03aca143cba063bae Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Thu, 6 Nov 2025 16:45:44 +0100 Subject: [PATCH] Standardize phone number formatting in member service and MembersView Integrated phone number standardization in the member service to ensure consistent formatting during member updates and contact creation. Added a new utility function in MembersView for phone number standardization, enhancing data integrity and user experience when adding or updating member information. --- backend/services/memberService.js | 19 +++- backend/utils/phoneUtils.js | 136 +++++++++++++++++++++++++++++ frontend/src/views/MembersView.vue | 128 ++++++++++++++++++++++++++- 3 files changed, 277 insertions(+), 6 deletions(-) create mode 100644 backend/utils/phoneUtils.js diff --git a/backend/services/memberService.js b/backend/services/memberService.js index 5db59f5..0107165 100644 --- a/backend/services/memberService.js +++ b/backend/services/memberService.js @@ -8,6 +8,7 @@ import fs from 'fs'; import sharp from 'sharp'; import { devLog } from '../utils/logger.js'; +import { standardizePhoneNumber } from '../utils/phoneUtils.js'; class MemberService { async getApprovalRequests(userToken, clubId) { await checkAccess(userToken, clubId); @@ -93,7 +94,7 @@ class MemberService { member.city = city; if (postalCode !== undefined) member.postalCode = postalCode; member.birthDate = birthdate; - member.phone = phone; + member.phone = phone ? standardizePhoneNumber(phone) : phone; member.email = email; member.active = active; member.testMembership = testMembership; @@ -111,10 +112,15 @@ class MemberService { // Create new contacts for (const contact of contacts) { if (contact.value && contact.type) { + // Standardisiere Telefonnummern + let standardizedValue = contact.value; + if (contact.type === 'phone') { + standardizedValue = standardizePhoneNumber(contact.value); + } await MemberContact.create({ memberId: member.id, type: contact.type, - value: contact.value, + value: standardizedValue, isParent: contact.isParent || false, parentName: contact.parentName || null, isPrimary: contact.isPrimary || false @@ -130,7 +136,7 @@ class MemberService { city: city, postalCode: postalCode || null, birthDate: birthdate, - phone: phone, + phone: phone ? standardizePhoneNumber(phone) : phone, email: email, clubId: clubId, active: active, @@ -146,10 +152,15 @@ class MemberService { if (Array.isArray(contacts)) { for (const contact of contacts) { if (contact.value && contact.type) { + // Standardisiere Telefonnummern + let standardizedValue = contact.value; + if (contact.type === 'phone') { + standardizedValue = standardizePhoneNumber(contact.value); + } await MemberContact.create({ memberId: newMember.id, type: contact.type, - value: contact.value, + value: standardizedValue, isParent: contact.isParent || false, parentName: contact.parentName || null, isPrimary: contact.isPrimary || false diff --git a/backend/utils/phoneUtils.js b/backend/utils/phoneUtils.js new file mode 100644 index 0000000..9d5db6f --- /dev/null +++ b/backend/utils/phoneUtils.js @@ -0,0 +1,136 @@ +/** + * Standardisiert eine Telefonnummer im Format: 0179 - 12 34 56 7 + * + * Die Vorwahl wird durch das erste Trennzeichen (Leerzeichen, Punkt, Bindestrich, + * Unterstrich, Schrägstrich) zwischen Zahlen identifiziert. + * + * @param {string} phoneNumber - Die zu standardisierende Telefonnummer + * @returns {string} - Die standardisierte Telefonnummer im Format "0179 - 12 34 56 7" + */ +function standardizePhoneNumber(phoneNumber) { + if (!phoneNumber || typeof phoneNumber !== 'string') { + return phoneNumber || ''; + } + + // Trennzeichen, die die Vorwahl abkapseln können + const separators = [' ', '.', '-', '_', '/']; + + // Entferne alle Zeichen außer Zahlen und Trennzeichen + let cleaned = phoneNumber.replace(/[^\d\s.\-_\/]/g, ''); + + // Extrahiere nur die Zahlen + const digitsOnly = cleaned.replace(/[^\d]/g, ''); + + if (digitsOnly.length === 0) { + return phoneNumber; // Falls keine Zahlen gefunden, Original zurückgeben + } + + // Finde das erste Trennzeichen zwischen Zahlen + let areaCodeEndIndex = -1; + for (let i = 0; i < cleaned.length - 1; i++) { + if (separators.includes(cleaned[i])) { + // Prüfe, ob vor und nach dem Trennzeichen eine Zahl steht + const beforeIsDigit = i > 0 && /\d/.test(cleaned[i - 1]); + const afterIsDigit = /\d/.test(cleaned[i + 1]); + if (beforeIsDigit && afterIsDigit) { + areaCodeEndIndex = i; + break; + } + } + } + + let areaCode = ''; + let rest = ''; + + if (areaCodeEndIndex !== -1) { + // Extrahiere die Vorwahl direkt aus dem String vor dem Trennzeichen + const beforeSeparator = cleaned.substring(0, areaCodeEndIndex); + areaCode = beforeSeparator.replace(/[^\d]/g, ''); // Nur Ziffern aus dem Teil vor dem Trennzeichen + + // Prüfe, ob die Vorwahl plausibel ist (typische deutsche Vorwahlen: 3-5 Ziffern) + // Wenn die Vorwahl zu lang ist (>=5 Ziffern) oder wenn nach dem Trennzeichen zu wenige Ziffern sind, + // versuche eine kürzere Vorwahl + if (digitsOnly.startsWith('0')) { + const afterSeparator = cleaned.substring(areaCodeEndIndex + 1); + const restDigits = afterSeparator.replace(/[^\d]/g, ''); + + // Wenn die Vorwahl >= 5 Ziffern hat oder wenn nach dem Trennzeichen zu wenige Ziffern sind (<6), + // versuche eine kürzere Vorwahl + if (areaCode.length >= 5 || (areaCode.length > 3 && restDigits.length < 6)) { + // Versuche typische Vorwahl-Längen: 4 für Mobilnummern (01xx), 3 für Festnetz + if (digitsOnly.substring(0, 2) === '01' && digitsOnly.length >= 4) { + areaCode = digitsOnly.substring(0, 4); + } else if (digitsOnly.length >= 3) { + areaCode = digitsOnly.substring(0, 3); + } + } + } + + // Finde die Position der Vorwahl in digitsOnly und extrahiere den Rest + // Die Vorwahl sollte am Anfang von digitsOnly stehen + if (digitsOnly.startsWith(areaCode)) { + rest = digitsOnly.substring(areaCode.length); + } else { + // Fallback: Wenn die Vorwahl nicht am Anfang steht, verwende die Länge + rest = digitsOnly.substring(areaCode.length); + } + } else { + // Kein Trennzeichen gefunden - versuche, die Vorwahl zu erraten + // Typische deutsche Vorwahlen: 3-5 Ziffern (z.B. 0179, 030, 040) + // Wenn die Nummer mit 0 beginnt, ist es wahrscheinlich eine Mobilnummer (4 Ziffern) oder Festnetz (3-5 Ziffern) + if (digitsOnly.startsWith('0')) { + // Mobilnummern beginnen meist mit 01 und haben 4 Ziffern Vorwahl + if (digitsOnly.length >= 4 && digitsOnly.substring(0, 2) === '01') { + areaCode = digitsOnly.substring(0, 4); + rest = digitsOnly.substring(4); + } else if (digitsOnly.length >= 3) { + // Festnetz: 3-5 Ziffern, versuche 3 als Standard + areaCode = digitsOnly.substring(0, 3); + rest = digitsOnly.substring(3); + } else { + // Zu kurz, keine Vorwahl + areaCode = ''; + rest = digitsOnly; + } + } else { + // Keine führende 0 - wahrscheinlich keine Vorwahl oder internationale Nummer + // Nehme die ersten 3-4 Ziffern als Vorwahl + if (digitsOnly.length >= 4) { + areaCode = digitsOnly.substring(0, 4); + rest = digitsOnly.substring(4); + } else { + areaCode = ''; + rest = digitsOnly; + } + } + } + + // Formatiere den Rest in Zweierblöcke + let formattedRest = ''; + for (let i = 0; i < rest.length; i += 2) { + if (i > 0) { + formattedRest += ' '; + } + // Wenn es die letzte Gruppe ist und nur eine Ziffer übrig ist, füge sie ohne Leerzeichen hinzu + if (i + 2 >= rest.length) { + formattedRest += rest.substring(i); + break; + } else { + formattedRest += rest.substring(i, i + 2); + } + } + + // Kombiniere Vorwahl und Rest + if (areaCode && formattedRest) { + return `${areaCode} - ${formattedRest}`; + } else if (areaCode) { + return areaCode; + } else if (formattedRest) { + return formattedRest; + } else { + return phoneNumber; // Fallback: Original zurückgeben + } +} + +export { standardizePhoneNumber }; + diff --git a/frontend/src/views/MembersView.vue b/frontend/src/views/MembersView.vue index 2776f9d..91592b4 100644 --- a/frontend/src/views/MembersView.vue +++ b/frontend/src/views/MembersView.vue @@ -643,6 +643,130 @@ export default { reader.readAsDataURL(file); } }, + standardizePhoneNumber(phoneNumber) { + if (!phoneNumber || typeof phoneNumber !== 'string') { + return phoneNumber || ''; + } + + // Trennzeichen, die die Vorwahl abkapseln können + const separators = [' ', '.', '-', '_', '/']; + + // Entferne alle Zeichen außer Zahlen und Trennzeichen + let cleaned = phoneNumber.replace(/[^\d\s.\-_\/]/g, ''); + + // Extrahiere nur die Zahlen + const digitsOnly = cleaned.replace(/[^\d]/g, ''); + + if (digitsOnly.length === 0) { + return phoneNumber; // Falls keine Zahlen gefunden, Original zurückgeben + } + + // Finde das erste Trennzeichen zwischen Zahlen + let areaCodeEndIndex = -1; + for (let i = 0; i < cleaned.length - 1; i++) { + if (separators.includes(cleaned[i])) { + // Prüfe, ob vor und nach dem Trennzeichen eine Zahl steht + const beforeIsDigit = i > 0 && /\d/.test(cleaned[i - 1]); + const afterIsDigit = /\d/.test(cleaned[i + 1]); + if (beforeIsDigit && afterIsDigit) { + areaCodeEndIndex = i; + break; + } + } + } + + let areaCode = ''; + let rest = ''; + + if (areaCodeEndIndex !== -1) { + // Extrahiere die Vorwahl direkt aus dem String vor dem Trennzeichen + const beforeSeparator = cleaned.substring(0, areaCodeEndIndex); + areaCode = beforeSeparator.replace(/[^\d]/g, ''); // Nur Ziffern aus dem Teil vor dem Trennzeichen + + // Prüfe, ob die Vorwahl plausibel ist (typische deutsche Vorwahlen: 3-5 Ziffern) + // Wenn die Vorwahl zu lang ist (>=5 Ziffern) oder wenn nach dem Trennzeichen zu wenige Ziffern sind, + // versuche eine kürzere Vorwahl + if (digitsOnly.startsWith('0')) { + const afterSeparator = cleaned.substring(areaCodeEndIndex + 1); + const restDigits = afterSeparator.replace(/[^\d]/g, ''); + + // Wenn die Vorwahl >= 5 Ziffern hat oder wenn nach dem Trennzeichen zu wenige Ziffern sind (<6), + // versuche eine kürzere Vorwahl + if (areaCode.length >= 5 || (areaCode.length > 3 && restDigits.length < 6)) { + // Versuche typische Vorwahl-Längen: 4 für Mobilnummern (01xx), 3 für Festnetz + if (digitsOnly.substring(0, 2) === '01' && digitsOnly.length >= 4) { + areaCode = digitsOnly.substring(0, 4); + } else if (digitsOnly.length >= 3) { + areaCode = digitsOnly.substring(0, 3); + } + } + } + + // Finde die Position der Vorwahl in digitsOnly und extrahiere den Rest + // Die Vorwahl sollte am Anfang von digitsOnly stehen + if (digitsOnly.startsWith(areaCode)) { + rest = digitsOnly.substring(areaCode.length); + } else { + // Fallback: Wenn die Vorwahl nicht am Anfang steht, verwende die Länge + rest = digitsOnly.substring(areaCode.length); + } + } else { + // Kein Trennzeichen gefunden - versuche, die Vorwahl zu erraten + // Typische deutsche Vorwahlen: 3-5 Ziffern (z.B. 0179, 030, 040) + // Wenn die Nummer mit 0 beginnt, ist es wahrscheinlich eine Mobilnummer (4 Ziffern) oder Festnetz (3-5 Ziffern) + if (digitsOnly.startsWith('0')) { + // Mobilnummern beginnen meist mit 01 und haben 4 Ziffern Vorwahl + if (digitsOnly.length >= 4 && digitsOnly.substring(0, 2) === '01') { + areaCode = digitsOnly.substring(0, 4); + rest = digitsOnly.substring(4); + } else if (digitsOnly.length >= 3) { + // Festnetz: 3-5 Ziffern, versuche 3 als Standard + areaCode = digitsOnly.substring(0, 3); + rest = digitsOnly.substring(3); + } else { + // Zu kurz, keine Vorwahl + areaCode = ''; + rest = digitsOnly; + } + } else { + // Keine führende 0 - wahrscheinlich keine Vorwahl oder internationale Nummer + // Nehme die ersten 3-4 Ziffern als Vorwahl + if (digitsOnly.length >= 4) { + areaCode = digitsOnly.substring(0, 4); + rest = digitsOnly.substring(4); + } else { + areaCode = ''; + rest = digitsOnly; + } + } + } + + // Formatiere den Rest in Zweierblöcke + let formattedRest = ''; + for (let i = 0; i < rest.length; i += 2) { + if (i > 0) { + formattedRest += ' '; + } + // Wenn es die letzte Gruppe ist und nur eine Ziffer übrig ist, füge sie ohne Leerzeichen hinzu + if (i + 2 >= rest.length) { + formattedRest += rest.substring(i); + break; + } else { + formattedRest += rest.substring(i, i + 2); + } + } + + // Kombiniere Vorwahl und Rest + if (areaCode && formattedRest) { + return `${areaCode} - ${formattedRest}`; + } else if (areaCode) { + return areaCode; + } else if (formattedRest) { + return formattedRest; + } else { + return phoneNumber; // Fallback: Original zurückgeben + } + }, async addNewMember() { // Prepare contacts array const contacts = []; @@ -650,7 +774,7 @@ export default { if (phone.value && phone.value.trim()) { contacts.push({ type: 'phone', - value: phone.value.trim(), + value: this.standardizePhoneNumber(phone.value.trim()), isParent: phone.isParent || false, parentName: phone.isParent ? (phone.parentName || null) : null, isPrimary: phone.isPrimary || false @@ -676,7 +800,7 @@ export default { city: this.newCity, postalCode: this.newPostalCode || null, birthdate: this.newBirthdate, - phone: this.newPhone, // Keep for backward compatibility + phone: this.newPhone ? this.standardizePhoneNumber(this.newPhone) : this.newPhone, // Keep for backward compatibility email: this.newEmail, // Keep for backward compatibility gender: this.newGender, active: this.newActive,