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.
This commit is contained in:
Torsten Schulz (local)
2025-11-06 16:45:44 +01:00
parent 9cdbd60a23
commit fc7b70b307
3 changed files with 277 additions and 6 deletions

View File

@@ -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

136
backend/utils/phoneUtils.js Normal file
View File

@@ -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 };

View File

@@ -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,