diff --git a/.codex b/.codex new file mode 100644 index 00000000..e69de29b diff --git a/backend/controllers/clubsController.js b/backend/controllers/clubsController.js index 7fc7e5c3..8b721fd4 100644 --- a/backend/controllers/clubsController.js +++ b/backend/controllers/clubsController.js @@ -60,12 +60,19 @@ export const updateClubSettings = async (req, res) => { try { const { authcode: token } = req.headers; const { clubid } = req.params; - const { greetingText, associationMemberNumber, myTischtennisFedNickname, autoFetchRankings } = req.body; + const { + greetingText, + associationMemberNumber, + myTischtennisFedNickname, + autoFetchRankings, + memberDataQualityRequirements + } = req.body; const updated = await ClubService.updateClubSettings(token, clubid, { greetingText, associationMemberNumber, myTischtennisFedNickname, - autoFetchRankings + autoFetchRankings, + memberDataQualityRequirements }); res.status(200).json(updated); } catch (error) { diff --git a/backend/migrations/20260415_add_member_data_quality_requirements_to_clubs.sql b/backend/migrations/20260415_add_member_data_quality_requirements_to_clubs.sql new file mode 100644 index 00000000..03fdfadd --- /dev/null +++ b/backend/migrations/20260415_add_member_data_quality_requirements_to_clubs.sql @@ -0,0 +1,6 @@ +-- Migration: Add per-club member data quality requirements. +-- Controls which optional contact/address fields count as required on /members. + +ALTER TABLE clubs + ADD COLUMN IF NOT EXISTS member_data_quality_requirements JSON NULL + COMMENT 'Configures which member fields are required for data quality checks'; diff --git a/backend/models/Club.js b/backend/models/Club.js index 1fe14861..ad9654a8 100644 --- a/backend/models/Club.js +++ b/backend/models/Club.js @@ -29,6 +29,12 @@ const Club = sequelize.define('Club', { defaultValue: false, field: 'auto_fetch_rankings', comment: 'Enable automatic TTR/QTTR rankings fetch for this club' + }, + memberDataQualityRequirements: { + type: DataTypes.JSON, + allowNull: true, + field: 'member_data_quality_requirements', + comment: 'Configures which member fields are required for data quality checks' } }, { tableName: 'clubs', diff --git a/backend/services/clubService.js b/backend/services/clubService.js index af315b45..a5239df0 100644 --- a/backend/services/clubService.js +++ b/backend/services/clubService.js @@ -71,7 +71,8 @@ class ClubService { greetingText, associationMemberNumber, myTischtennisFedNickname, - autoFetchRankings + autoFetchRankings, + memberDataQualityRequirements }) { await checkAccess(userToken, clubId); const club = await Club.findByPk(clubId); @@ -81,9 +82,33 @@ class ClubService { const updates = { greetingText, associationMemberNumber }; if (myTischtennisFedNickname !== undefined) updates.myTischtennisFedNickname = myTischtennisFedNickname || null; if (autoFetchRankings !== undefined) updates.autoFetchRankings = !!autoFetchRankings; + if (memberDataQualityRequirements !== undefined) { + updates.memberDataQualityRequirements = this.normalizeMemberDataQualityRequirements(memberDataQualityRequirements); + } return await club.update(updates); } + normalizeMemberDataQualityRequirements(settings) { + const defaults = { + requireStreet: true, + requirePostalCode: true, + requireCity: true, + requirePhone: true, + requireEmail: true + }; + + if (!settings || typeof settings !== 'object' || Array.isArray(settings)) { + return defaults; + } + + return Object.fromEntries( + Object.entries(defaults).map(([key, defaultValue]) => [ + key, + typeof settings[key] === 'boolean' ? settings[key] : defaultValue + ]) + ); + } + async approveUserClubAccess(userToken, clubId, toApproveUserId) { await checkAccess(userToken, clubId); const toApproveUserClub = await UserClub.findOne({ diff --git a/frontend/src/i18n/locales/de-CH.json b/frontend/src/i18n/locales/de-CH.json index af04507e..b090e9a1 100644 --- a/frontend/src/i18n/locales/de-CH.json +++ b/frontend/src/i18n/locales/de-CH.json @@ -644,6 +644,9 @@ "dataIssuePhone": "Telefon fehlt", "dataIssueEmail": "E-Mail fehlt", "dataIssueAddress": "Adresse fehlt", + "dataIssueStreet": "Strasse fehlt", + "dataIssuePostalCode": "PLZ fehlt", + "dataIssueCity": "Ort fehlt", "dataIssueGender": "Geschlecht ungeklärt", "dataIssueTrainingGroup": "Trainingsgruppe fehlt", "openTasks": "Offene Aufgaben", diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json index 19d1f5e6..dc950bd7 100644 --- a/frontend/src/i18n/locales/de.json +++ b/frontend/src/i18n/locales/de.json @@ -392,6 +392,9 @@ "dataIssuePhone": "Telefon fehlt", "dataIssueEmail": "E-Mail fehlt", "dataIssueAddress": "Adresse fehlt", + "dataIssueStreet": "Straße fehlt", + "dataIssuePostalCode": "PLZ fehlt", + "dataIssueCity": "Ort fehlt", "dataIssueGender": "Geschlecht ungeklärt", "dataIssueTrainingGroup": "Trainingsgruppe fehlt", "openTasks": "Offene Aufgaben", @@ -1201,7 +1204,14 @@ "autoFetchRankings": "Ranglisten automatisch abrufen", "myTischtennisFedNickname": "Verbandskürzel", "myTischtennisFedNicknamePlaceholder": "z. B. HeTTV", - "rankingsUsesAssociationNumber": "Die Vereinsnummer für den Ranglisten-Abruf entspricht der Verbands-Mitgliedsnummer oben." + "rankingsUsesAssociationNumber": "Die Vereinsnummer für den Ranglisten-Abruf entspricht der Verbands-Mitgliedsnummer oben.", + "memberDataQuality": "Datenqualität Mitglieder", + "memberDataQualityHint": "Diese Felder zählen auf der Mitgliederseite als nötig. Alle Felder bleiben weiterhin eingebbar.", + "requireStreet": "Straße nötig", + "requirePostalCode": "PLZ nötig", + "requireCity": "Ort nötig", + "requirePhone": "Telefonnummer nötig", + "requireEmail": "E-Mail-Adresse nötig" }, "predefinedActivities": { "title": "Vordefinierte Aktivitäten", diff --git a/frontend/src/i18n/locales/en-AU.json b/frontend/src/i18n/locales/en-AU.json index ffef64e6..e36d172b 100644 --- a/frontend/src/i18n/locales/en-AU.json +++ b/frontend/src/i18n/locales/en-AU.json @@ -641,6 +641,9 @@ "dataIssuePhone": "Phone missing", "dataIssueEmail": "Email missing", "dataIssueAddress": "Address missing", + "dataIssueStreet": "Street missing", + "dataIssuePostalCode": "Postcode missing", + "dataIssueCity": "Suburb/city missing", "dataIssueGender": "Gender unclear", "dataIssueTrainingGroup": "Training group missing", "openTasks": "Open tasks", diff --git a/frontend/src/i18n/locales/en-GB.json b/frontend/src/i18n/locales/en-GB.json index 0ab43dc9..6be339c7 100644 --- a/frontend/src/i18n/locales/en-GB.json +++ b/frontend/src/i18n/locales/en-GB.json @@ -916,6 +916,9 @@ "dataIssuePhone": "Phone missing", "dataIssueEmail": "Email missing", "dataIssueAddress": "Address missing", + "dataIssueStreet": "Street missing", + "dataIssuePostalCode": "Postcode missing", + "dataIssueCity": "Town/city missing", "dataIssueGender": "Gender unclear", "dataIssueTrainingGroup": "Training group missing", "openTasks": "Open tasks", diff --git a/frontend/src/i18n/locales/en-US.json b/frontend/src/i18n/locales/en-US.json index a4e27c2f..3a1bec0d 100644 --- a/frontend/src/i18n/locales/en-US.json +++ b/frontend/src/i18n/locales/en-US.json @@ -641,6 +641,9 @@ "dataIssuePhone": "Phone missing", "dataIssueEmail": "Email missing", "dataIssueAddress": "Address missing", + "dataIssueStreet": "Street missing", + "dataIssuePostalCode": "Postal code missing", + "dataIssueCity": "City missing", "dataIssueGender": "Gender unclear", "dataIssueTrainingGroup": "Training group missing", "openTasks": "Open tasks", diff --git a/frontend/src/i18n/locales/es.json b/frontend/src/i18n/locales/es.json index 76a304cf..894d6ac7 100644 --- a/frontend/src/i18n/locales/es.json +++ b/frontend/src/i18n/locales/es.json @@ -604,6 +604,9 @@ "dataIssuePhone": "Falta el teléfono", "dataIssueEmail": "Falta el correo", "dataIssueAddress": "Falta la dirección", + "dataIssueStreet": "Falta la calle", + "dataIssuePostalCode": "Falta el código postal", + "dataIssueCity": "Falta la ciudad", "dataIssueGender": "Sexo no definido", "dataIssueTrainingGroup": "Falta el grupo de entrenamiento", "openTasks": "Tareas abiertas", diff --git a/frontend/src/i18n/locales/fil.json b/frontend/src/i18n/locales/fil.json index 91fe076f..1a035e3e 100644 --- a/frontend/src/i18n/locales/fil.json +++ b/frontend/src/i18n/locales/fil.json @@ -604,6 +604,9 @@ "dataIssuePhone": "Kulang ang telepono", "dataIssueEmail": "Kulang ang email", "dataIssueAddress": "Kulang ang address", + "dataIssueStreet": "Kulang ang kalye", + "dataIssuePostalCode": "Kulang ang postal code", + "dataIssueCity": "Kulang ang lungsod", "dataIssueGender": "Hindi malinaw ang kasarian", "dataIssueTrainingGroup": "Kulang ang grupo ng pagsasanay", "openTasks": "Open tasks", diff --git a/frontend/src/i18n/locales/fr.json b/frontend/src/i18n/locales/fr.json index 5b8c81b4..12a4f370 100644 --- a/frontend/src/i18n/locales/fr.json +++ b/frontend/src/i18n/locales/fr.json @@ -604,6 +604,9 @@ "dataIssuePhone": "Téléphone manquant", "dataIssueEmail": "E-mail manquant", "dataIssueAddress": "Adresse manquante", + "dataIssueStreet": "Rue manquante", + "dataIssuePostalCode": "Code postal manquant", + "dataIssueCity": "Ville manquante", "dataIssueGender": "Sexe non défini", "dataIssueTrainingGroup": "Groupe d'entraînement manquant", "openTasks": "Tâches ouvertes", diff --git a/frontend/src/i18n/locales/it.json b/frontend/src/i18n/locales/it.json index 6f0afa1b..dfe54861 100644 --- a/frontend/src/i18n/locales/it.json +++ b/frontend/src/i18n/locales/it.json @@ -604,6 +604,9 @@ "dataIssuePhone": "Telefono mancante", "dataIssueEmail": "E-mail mancante", "dataIssueAddress": "Indirizzo mancante", + "dataIssueStreet": "Via mancante", + "dataIssuePostalCode": "CAP mancante", + "dataIssueCity": "Città mancante", "dataIssueGender": "Genere non definito", "dataIssueTrainingGroup": "Gruppo di allenamento mancante", "openTasks": "Attività aperte", diff --git a/frontend/src/i18n/locales/ja.json b/frontend/src/i18n/locales/ja.json index e7eb60e9..c19c8406 100644 --- a/frontend/src/i18n/locales/ja.json +++ b/frontend/src/i18n/locales/ja.json @@ -604,6 +604,9 @@ "dataIssuePhone": "電話番号がありません", "dataIssueEmail": "メールアドレスがありません", "dataIssueAddress": "住所がありません", + "dataIssueStreet": "番地がありません", + "dataIssuePostalCode": "郵便番号がありません", + "dataIssueCity": "市区町村がありません", "dataIssueGender": "性別が未設定です", "dataIssueTrainingGroup": "練習グループがありません", "openTasks": "Open tasks", diff --git a/frontend/src/i18n/locales/pl.json b/frontend/src/i18n/locales/pl.json index c62d3368..cb564932 100644 --- a/frontend/src/i18n/locales/pl.json +++ b/frontend/src/i18n/locales/pl.json @@ -604,6 +604,9 @@ "dataIssuePhone": "Brak telefonu", "dataIssueEmail": "Brak e-maila", "dataIssueAddress": "Brak adresu", + "dataIssueStreet": "Brak ulicy", + "dataIssuePostalCode": "Brak kodu pocztowego", + "dataIssueCity": "Brak miasta", "dataIssueGender": "Płeć nieokreślona", "dataIssueTrainingGroup": "Brak grupy treningowej", "openTasks": "Otwarte zadania", diff --git a/frontend/src/i18n/locales/th.json b/frontend/src/i18n/locales/th.json index 56d5f42f..81ca3fe2 100644 --- a/frontend/src/i18n/locales/th.json +++ b/frontend/src/i18n/locales/th.json @@ -604,6 +604,9 @@ "dataIssuePhone": "ไม่มีหมายเลขโทรศัพท์", "dataIssueEmail": "ไม่มีอีเมล", "dataIssueAddress": "ไม่มีที่อยู่", + "dataIssueStreet": "ไม่มีถนน", + "dataIssuePostalCode": "ไม่มีรหัสไปรษณีย์", + "dataIssueCity": "ไม่มีเมือง", "dataIssueGender": "เพศยังไม่ชัดเจน", "dataIssueTrainingGroup": "ไม่มีกลุ่มฝึกซ้อม", "openTasks": "Open tasks", diff --git a/frontend/src/i18n/locales/tl.json b/frontend/src/i18n/locales/tl.json index 51a9f6a6..04e6fbaf 100644 --- a/frontend/src/i18n/locales/tl.json +++ b/frontend/src/i18n/locales/tl.json @@ -604,6 +604,9 @@ "dataIssuePhone": "Kulang ang telepono", "dataIssueEmail": "Kulang ang email", "dataIssueAddress": "Kulang ang address", + "dataIssueStreet": "Kulang ang kalye", + "dataIssuePostalCode": "Kulang ang postal code", + "dataIssueCity": "Kulang ang lungsod", "dataIssueGender": "Hindi malinaw ang kasarian", "dataIssueTrainingGroup": "Kulang ang grupo ng pagsasanay", "openTasks": "Open tasks", diff --git a/frontend/src/i18n/locales/zh.json b/frontend/src/i18n/locales/zh.json index 242cfda1..d724c7a4 100644 --- a/frontend/src/i18n/locales/zh.json +++ b/frontend/src/i18n/locales/zh.json @@ -604,6 +604,9 @@ "dataIssuePhone": "缺少电话号码", "dataIssueEmail": "缺少电子邮箱地址", "dataIssueAddress": "缺少地址", + "dataIssueStreet": "缺少街道", + "dataIssuePostalCode": "缺少邮政编码", + "dataIssueCity": "缺少城市", "dataIssueGender": "性别未明确", "dataIssueTrainingGroup": "缺少训练组", "openTasks": "Open tasks", diff --git a/frontend/src/views/ClubSettings.vue b/frontend/src/views/ClubSettings.vue index 97429a0c..79c7424d 100644 --- a/frontend/src/views/ClubSettings.vue +++ b/frontend/src/views/ClubSettings.vue @@ -69,6 +69,33 @@
{{ $t('clubSettings.rankingsUsesAssociationNumber') }}
+{{ $t('clubSettings.memberDataQualityHint') }}
+ +