feat(match-report): implement test mode simulation for submission validation
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 56s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 56s
This commit is contained in:
@@ -101,6 +101,33 @@
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div v-if="isTestMode" class="test-simulation">
|
||||
<button type="button" class="btn-secondary" @click="simulateSubmission">
|
||||
🧪 Übermittlung inklusive nuScore-Validierung simulieren
|
||||
</button>
|
||||
<p>Keine Netzwerkverbindung: Die Simulation entfernt Doppelpositionen wie eine normalisierte
|
||||
Validierungsantwort und prüft, ob der finale Payload sie wiederherstellt.</p>
|
||||
<div v-if="simulationResult" class="test-submit-result"
|
||||
:class="simulationResult.valid ? 'test-success' : 'test-error'">
|
||||
<strong>{{ simulationResult.valid ? '✓ Doppel sind im finalen Payload vollständig' : '⚠ Doppel fehlen im finalen Payload' }}</strong>
|
||||
<p>{{ simulationResult.summary }}</p>
|
||||
<ul v-if="simulationResult.messages.length">
|
||||
<li v-for="message in simulationResult.messages" :key="message">{{ message }}</li>
|
||||
</ul>
|
||||
<div v-if="simulationResult.doubles.length" class="comparison-table-wrap">
|
||||
<table class="comparison-table">
|
||||
<thead><tr><th>Spiel</th><th>Heim</th><th>Gast</th><th>Prüfung</th></tr></thead>
|
||||
<tbody>
|
||||
<tr v-for="row in simulationResult.doubles" :key="`simulation-double-${row.matchNumber}`">
|
||||
<td>{{ row.matchNumber }}</td><td>{{ row.home }}</td><td>{{ row.guest }}</td>
|
||||
<td>{{ row.valid ? '✓ vollständig' : '✕ unvollständig' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="isTestMode && (meetingDetails?.isCompleted || meetingData?.isCompleted)" class="test-verification">
|
||||
<button type="button" class="btn-secondary" @click="verifyCompletedMatchProjection">
|
||||
🧪 Gespeicherte Paarungen prüfen
|
||||
@@ -875,6 +902,7 @@ export default {
|
||||
isTestMode: this.standaloneTest,
|
||||
testVerification: null,
|
||||
testSubmissionResult: null,
|
||||
simulationResult: null,
|
||||
lastLiveUpdatedAt: null,
|
||||
originalTestInput: null,
|
||||
// Unveränderliche Referenz auf den offiziellen Bericht, bevor der
|
||||
@@ -1213,6 +1241,7 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
// Ein früherer Prüfhinweis gilt nach einer lokalen Änderung nicht mehr.
|
||||
this.testVerification = null;
|
||||
this.testSubmissionResult = null;
|
||||
this.simulationResult = null;
|
||||
this.persistLocalDraftDebounced();
|
||||
},
|
||||
deep: true
|
||||
@@ -1223,6 +1252,7 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
// machen einen bereits angezeigten Abgleich hinfällig.
|
||||
this.testVerification = null;
|
||||
this.testSubmissionResult = null;
|
||||
this.simulationResult = null;
|
||||
this.persistLocalDraftDebounced();
|
||||
},
|
||||
deep: true
|
||||
@@ -1984,6 +2014,83 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
};
|
||||
},
|
||||
|
||||
// Bildet den kritischen Teil des echten Submit-Ablaufs lokal nach.
|
||||
// NuScore darf den Payload bei /validate normalisieren; genau dabei
|
||||
// waren zuvor die Doppelpositionen verloren gegangen.
|
||||
simulateSubmission() {
|
||||
if (!this.meetingDetails) {
|
||||
return;
|
||||
}
|
||||
|
||||
const initialPayload = JSON.parse(JSON.stringify(this.match || {}));
|
||||
this.updateMatchData(initialPayload, { finalizeReport: false });
|
||||
|
||||
// Simulierte Antwort von /validate: Die Doppelinformationen sind
|
||||
// nicht mehr vorhanden. Der Produktionscode muss sie anschließend
|
||||
// aus dem lokalen Aufstellungszustand wieder einsetzen.
|
||||
const normalizedByNuScore = JSON.parse(JSON.stringify(initialPayload));
|
||||
['teamLineupHomePlayers', 'teamLineupGuestPlayers'].forEach(teamKey => {
|
||||
(normalizedByNuScore[teamKey] || []).forEach(player => {
|
||||
player.positionDouble = null;
|
||||
player.positions = (player.positions || []).filter(position => !/^D\d+$/i.test(String(position)));
|
||||
});
|
||||
});
|
||||
|
||||
const finalPayload = JSON.parse(JSON.stringify(normalizedByNuScore));
|
||||
this.applyCurrentLineupStateToPayload(finalPayload);
|
||||
this.updatePlayerPositions(finalPayload);
|
||||
|
||||
const doubles = this.getSimulatedDoubleRows(finalPayload);
|
||||
const invalidRows = doubles.filter(row => !row.valid);
|
||||
const messages = invalidRows.map(row =>
|
||||
`Spiel ${row.matchNumber}: ${row.home} – ${row.guest}`
|
||||
);
|
||||
if (doubles.length === 0) {
|
||||
messages.push('Das gewählte Spielsystem enthält keine Doppelbegegnung.');
|
||||
}
|
||||
|
||||
this.simulationResult = {
|
||||
valid: doubles.length > 0 && invalidRows.length === 0,
|
||||
summary: invalidRows.length === 0 && doubles.length > 0
|
||||
? `Die ${doubles.length} Doppelbegegnung(en) bleiben nach der simulierten Validierung vollständig zugeordnet.`
|
||||
: 'Mindestens eine Doppelbegegnung wäre im finalen Payload nicht vollständig besetzt.',
|
||||
messages,
|
||||
doubles,
|
||||
payload: finalPayload
|
||||
};
|
||||
},
|
||||
|
||||
getSimulatedDoubleRows(payload) {
|
||||
const formations = payload?.meetingPlayMode?.matchFormations || this.meetingDetails?.meetingPlayMode?.matchFormations || [];
|
||||
const nameOf = (player) => [player?.firstname || player?.firstName || '', player?.lastname || player?.lastName || '']
|
||||
.filter(Boolean).join(' ').trim() || 'nicht anwesend';
|
||||
const pairFor = (teamKey, position) => {
|
||||
const players = (payload?.[teamKey] || [])
|
||||
.filter(player => Number(player?.positionDouble) === position && Number(player?.nuLigaPersonId) > 0)
|
||||
.map(nameOf);
|
||||
return {
|
||||
label: players.length === 2 ? players.join(' / ') : (players.join(' / ') || 'nicht anwesend / nicht anwesend'),
|
||||
complete: players.length === 2
|
||||
};
|
||||
};
|
||||
|
||||
return formations.map((formation, index) => {
|
||||
const homePosition = String(formation?.homeLineupPosition || '');
|
||||
const guestPosition = String(formation?.guestLineupPosition || '');
|
||||
if (!/^D\d+$/i.test(homePosition) || !/^D\d+$/i.test(guestPosition)) {
|
||||
return null;
|
||||
}
|
||||
const home = pairFor('teamLineupHomePlayers', Number(homePosition.slice(1)));
|
||||
const guest = pairFor('teamLineupGuestPlayers', Number(guestPosition.slice(1)));
|
||||
return {
|
||||
matchNumber: formation?.matchNr || index + 1,
|
||||
home: home.label,
|
||||
guest: guest.label,
|
||||
valid: home.complete && guest.complete
|
||||
};
|
||||
}).filter(Boolean);
|
||||
},
|
||||
|
||||
compareTestResultsToOriginal() {
|
||||
const originalRows = this.getOriginalReportRows();
|
||||
if (!originalRows.length) {
|
||||
@@ -2138,6 +2245,15 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
const submitPayload = validationResult?.object
|
||||
? JSON.parse(JSON.stringify(validationResult.object))
|
||||
: matchData;
|
||||
|
||||
// nuScore kann den durch /validate zurückgegebenen Entwurf
|
||||
// normalisieren und dabei Doppelpositionen verwerfen. Für den
|
||||
// finalen Submit müssen deshalb die gerade bestätigten lokalen
|
||||
// Aufstellungen erneut in genau diesen Rückgabedatensatz
|
||||
// übernommen werden. Andernfalls werden die Doppel im
|
||||
// Berichtsbogen als "nicht anwesend" dargestellt.
|
||||
this.applyCurrentLineupStateToPayload(submitPayload);
|
||||
this.updatePlayerPositions(submitPayload);
|
||||
submitPayload.isCompleted = true;
|
||||
if (!submitPayload.signature || typeof submitPayload.signature !== 'object') {
|
||||
submitPayload.signature = {};
|
||||
@@ -2525,7 +2641,11 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
...player,
|
||||
isSelected: Boolean(localPlayer.isSelected),
|
||||
positionSingle: localPlayer.isSelected ? (localPlayer.positionSingle || null) : null,
|
||||
positionDouble: localPlayer.isSelected ? (localPlayer.positionDouble || null) : null,
|
||||
// Doppel können auch von einem nur für das Doppel
|
||||
// vorgesehenen Ersatzspieler belegt werden. Diese
|
||||
// Position darf daher nicht an isSelected gekoppelt
|
||||
// sein und muss die Validierungsantwort überleben.
|
||||
positionDouble: localPlayer.positionDouble || null,
|
||||
positions: Array.isArray(localPlayer.positions) ? [...localPlayer.positions] : []
|
||||
};
|
||||
});
|
||||
@@ -2734,6 +2854,7 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
|
||||
resetStandaloneTest() {
|
||||
this.initializeStandaloneTestData();
|
||||
this.simulationResult = null;
|
||||
},
|
||||
|
||||
buildStandaloneTestPayload() {
|
||||
@@ -3484,6 +3605,7 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
this.initialCompletionState = false;
|
||||
this.testVerification = null;
|
||||
this.testSubmissionResult = null;
|
||||
this.simulationResult = null;
|
||||
this.activeSection = 'general';
|
||||
|
||||
await this.$nextTick();
|
||||
|
||||
Reference in New Issue
Block a user