Fix match result showing under/over instead of actual fight winner

- Fixed bug in _update_bet_results where match.result was incorrectly set to UNDER/OVER when selected_result was UNDER/OVER
- Now properly looks for actual fight winner (WIN1, DRAW, WIN2, etc.) from winning outcomes or match outcomes
- Added print functionality to fixture details page with options to:
  - Print all matches or last N matches
  - Include/exclude completed or pending matches
- Added print-specific CSS for clean printouts
parent 80831a5b
......@@ -3744,22 +3744,40 @@ class GamesThread(ThreadedComponent):
logger.info(f"DEBUG _update_bet_results: Using pre-filtered winning outcomes: {winning_outcome_names}")
# Set the main result
# If selected_result is UNDER/OVER, use the first winning outcome as the main result
# (the actual fight winner like WIN1, X, WIN2)
if selected_result in ['UNDER', 'OVER'] and extraction_winning_outcome_names:
# Find the first non-UNDER/OVER winning outcome as the main result
# The match.result should always contain the actual fight winner (WIN1, DRAW, WIN2, etc.)
# not UNDER/OVER which is stored separately in under_over_result
if selected_result in ['UNDER', 'OVER']:
# selected_result is UNDER/OVER, we need to find the actual fight winner
main_result = None
for outcome in extraction_winning_outcome_names:
if outcome not in ['UNDER', 'OVER']:
main_result = outcome
break
# First, try to find a non-UNDER/OVER outcome from extraction winning outcomes
if extraction_winning_outcome_names:
for outcome in extraction_winning_outcome_names:
if outcome not in ['UNDER', 'OVER']:
main_result = outcome
logger.info(f"DEBUG _update_bet_results: Found fight winner '{main_result}' from extraction_winning_outcome_names")
break
# If not found in winning outcomes, look in match outcomes
if not main_result:
# Get all match outcomes and find a fight winner (non-UNDER/OVER outcome)
match_outcomes = session.query(MatchOutcomeModel).filter(
MatchOutcomeModel.match_id == match_id
).all()
for outcome in match_outcomes:
if outcome.column_name not in ['UNDER', 'OVER']:
main_result = outcome.column_name
logger.info(f"DEBUG _update_bet_results: Found fight winner '{main_result}' from match outcomes")
break
# Set the result
if main_result:
match.result = main_result
logger.info(f"DEBUG _update_bet_results: selected_result is UNDER/OVER, set match.result to '{main_result}' from winning outcomes")
logger.info(f"DEBUG _update_bet_results: selected_result is UNDER/OVER, set match.result to fight winner '{main_result}'")
else:
# If no non-UNDER/OVER outcome found, use selected_result
# Ultimate fallback - use selected_result but log warning
match.result = selected_result
logger.info(f"DEBUG _update_bet_results: No non-UNDER/OVER winning outcome found, set match.result to '{selected_result}'")
logger.warning(f"DEBUG _update_bet_results: Could not find fight winner, using selected_result '{selected_result}' as fallback")
else:
match.result = selected_result
logger.info(f"DEBUG _update_bet_results: Set match.result to '{selected_result}'")
......
......@@ -19,9 +19,69 @@
<h1><i class="fas fa-boxing me-2"></i>Fixture Details</h1>
<p class="mb-0 text-muted">All matches in this fixture</p>
</div>
<a href="{{ url_for('main.fixtures') }}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-1"></i>Back to Fixtures
</a>
<div class="d-flex gap-2">
<button type="button" class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#printModal">
<i class="fas fa-print me-1"></i>Print
</button>
<a href="{{ url_for('main.fixtures') }}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-1"></i>Back to Fixtures
</a>
</div>
</div>
<!-- Print Modal -->
<div class="modal fade" id="printModal" tabindex="-1" aria-labelledby="printModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="printModalLabel"><i class="fas fa-print me-2"></i>Print Fixture Details</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Print Options</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="printOption" id="printAll" value="all" checked>
<label class="form-check-label" for="printAll">
Print all matches
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="printOption" id="printLastN" value="lastN">
<label class="form-check-label" for="printLastN">
Print last N matches
</label>
</div>
</div>
<div class="mb-3" id="lastNInput" style="display: none;">
<label for="numberOfMatches" class="form-label">Number of matches to print (from the end)</label>
<input type="number" class="form-control" id="numberOfMatches" min="1" value="10">
<div class="form-text">Enter the number of most recent matches to include in the printout.</div>
</div>
<div class="mb-3">
<label class="form-label">Include</label>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="includeCompleted" checked>
<label class="form-check-label" for="includeCompleted">
Completed matches (with results)
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="includePending" checked>
<label class="form-check-label" for="includePending">
Pending/Upcoming matches
</label>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="printFixture()">
<i class="fas fa-print me-1"></i>Print
</button>
</div>
</div>
</div>
</div>
<!-- Loading Spinner -->
......@@ -628,5 +688,374 @@ function getUploadStatusBadge(match) {
return '<span class="badge bg-secondary"><i class="fas fa-clock me-1"></i>Pending</span>';
}
}
// Print functionality
document.addEventListener('DOMContentLoaded', function() {
// Toggle last N input visibility
document.querySelectorAll('input[name="printOption"]').forEach(radio => {
radio.addEventListener('change', function() {
const lastNInput = document.getElementById('lastNInput');
if (this.value === 'lastN') {
lastNInput.style.display = 'block';
} else {
lastNInput.style.display = 'none';
}
});
});
});
function printFixture() {
const printOption = document.querySelector('input[name="printOption"]:checked').value;
const includeCompleted = document.getElementById('includeCompleted').checked;
const includePending = document.getElementById('includePending').checked;
let matchesToPrint = [];
if (cachedFixtureData) {
const data = JSON.parse(cachedFixtureData);
let matches = [...data.matches];
// Filter by completion status
if (!includeCompleted || !includePending) {
matches = matches.filter(match => {
const isCompleted = match.status === 'done' || match.status === 'cancelled' || match.status === 'failed';
if (includeCompleted && isCompleted) return true;
if (includePending && !isCompleted) return true;
return false;
});
}
// Apply last N filter
if (printOption === 'lastN') {
const n = parseInt(document.getElementById('numberOfMatches').value) || 10;
matchesToPrint = matches.slice(-n);
} else {
matchesToPrint = matches;
}
}
// Generate print content
const printContent = generatePrintContent(matchesToPrint);
// Create print window
const printWindow = window.open('', '_blank');
printWindow.document.write(printContent);
printWindow.document.close();
printWindow.focus();
// Close modal
const modal = bootstrap.Modal.getInstance(document.getElementById('printModal'));
if (modal) {
modal.hide();
}
// Trigger print after a short delay
setTimeout(() => {
printWindow.print();
}, 500);
}
function generatePrintContent(matches) {
const fixtureInfo = JSON.parse(cachedFixtureData).fixture;
const now = new Date().toLocaleString();
// Calculate summary statistics
let completedCount = 0;
matches.forEach(match => {
if (match.status === 'done') {
completedCount++;
}
});
let matchesHTML = '';
matches.forEach((match, index) => {
const startTimeDisplay = match.start_time ? new Date(match.start_time).toLocaleString() : 'Not set';
const endTimeDisplay = match.end_time ? new Date(match.end_time).toLocaleString() : 'Not set';
const resultDisplay = match.result || 'N/A';
const winningOutcomesDisplay = formatWinningOutcomes(match.winning_outcomes);
const underOverDisplay = match.under_over_result || 'N/A';
const isCompleted = match.status === 'done' || match.status === 'cancelled' || match.status === 'failed';
const rowClass = isCompleted ? 'completed' : 'pending';
const statusText = match.status.toUpperCase();
matchesHTML += `
<tr class="${rowClass}">
<td style="text-align: center; font-weight: bold;">#${match.match_number}</td>
<td>
<div style="font-weight: bold;">${match.fighter1_township}</div>
<div style="font-size: 10px; color: #666;">vs</div>
<div style="font-weight: bold;">${match.fighter2_township}</div>
</td>
<td style="text-align: center;">${statusText}</td>
<td style="font-size: 11px;">${startTimeDisplay}</td>
<td style="font-size: 11px;">${endTimeDisplay}</td>
<td style="text-align: center; font-weight: bold;">${resultDisplay}</td>
<td style="font-size: 11px;">${winningOutcomesDisplay}</td>
<td style="text-align: center;">${underOverDisplay}</td>
</tr>
`;
});
return `
<!DOCTYPE html>
<html>
<head>
<title>Fixture Details - ${fixtureInfo.fixture_id}</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
font-size: 12px;
line-height: 1.4;
color: #333;
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 20px;
border-bottom: 2px solid #333;
padding-bottom: 15px;
}
.header h1 {
font-size: 24px;
margin-bottom: 5px;
}
.header .subtitle {
font-size: 14px;
color: #666;
}
.fixture-info {
margin-bottom: 20px;
padding: 10px;
background: #f5f5f5;
border-radius: 5px;
}
.fixture-info table {
width: 100%;
}
.fixture-info td {
padding: 3px 10px;
}
.fixture-info td:first-child {
font-weight: bold;
width: 150px;
}
.summary {
margin-bottom: 20px;
display: flex;
justify-content: space-between;
}
.summary-box {
text-align: center;
padding: 10px 20px;
border: 1px solid #ddd;
border-radius: 5px;
}
.summary-box .number {
font-size: 24px;
font-weight: bold;
color: #333;
}
.summary-box .label {
font-size: 11px;
color: #666;
}
table.matches {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
table.matches th {
background: #333;
color: white;
padding: 8px 5px;
text-align: center;
font-size: 11px;
}
table.matches td {
border: 1px solid #ddd;
padding: 6px 5px;
vertical-align: middle;
}
table.matches tr.completed {
background: #f9f9f9;
}
table.matches tr.pending {
background: #fff;
}
.footer {
text-align: center;
font-size: 10px;
color: #666;
margin-top: 20px;
border-top: 1px solid #ddd;
padding-top: 10px;
}
.legend {
margin-bottom: 15px;
font-size: 11px;
}
.legend span {
margin-right: 20px;
}
.legend .completed-indicator {
display: inline-block;
width: 12px;
height: 12px;
background: #f0f0f0;
border: 1px solid #ccc;
margin-right: 3px;
}
@media print {
body {
padding: 0;
}
.no-print {
display: none;
}
}
</style>
</head>
<body>
<div class="header">
<h1>Fixture Report</h1>
<div class="subtitle">Generated on ${now}</div>
</div>
<div class="fixture-info">
<table>
<tr>
<td>Fixture ID:</td>
<td>${fixtureInfo.fixture_id}</td>
</tr>
<tr>
<td>Venue:</td>
<td>${fixtureInfo.venue_kampala_township || 'N/A'}</td>
</tr>
<tr>
<td>Total Matches:</td>
<td>${matches.length}</td>
</tr>
<tr>
<td>Completed:</td>
<td>${completedCount}</td>
</tr>
</table>
</div>
<div class="summary">
<div class="summary-box">
<div class="number">${matches.length}</div>
<div class="label">Total Matches</div>
</div>
<div class="summary-box">
<div class="number">${completedCount}</div>
<div class="label">Completed</div>
</div>
<div class="summary-box">
<div class="number">${matches.length - completedCount}</div>
<div class="label">Pending</div>
</div>
</div>
<div class="legend">
<span><span class="completed-indicator"></span> Completed Match</span>
<span>Result: Fight winner (WIN1, DRAW, WIN2, etc.)</span>
<span>Under/Over: Total goals result</span>
</div>
<table class="matches">
<thead>
<tr>
<th style="width: 50px;">#</th>
<th style="width: 150px;">Fighters</th>
<th style="width: 60px;">Status</th>
<th style="width: 100px;">Start Time</th>
<th style="width: 100px;">End Time</th>
<th style="width: 60px;">Result</th>
<th style="width: 100px;">Winning Outcomes</th>
<th style="width: 60px;">U/O</th>
</tr>
</thead>
<tbody>
${matchesHTML}
</tbody>
</table>
<div class="footer">
<p>This report was generated by MbetterClient System</p>
<p>Fixture ID: ${fixtureInfo.fixture_id} | Printed: ${now}</p>
</div>
</body>
</html>
`;
}
</script>
<!-- Print-specific styles -->
<style media="print">
/* Hide non-essential elements when printing the main page */
.breadcrumb,
.btn,
.modal,
#loading,
#error-message,
nav {
display: none !important;
}
/* Ensure content takes full width */
.container-fluid {
width: 100% !important;
max-width: none !important;
padding: 0 !important;
}
/* Adjust card styling for print */
.card {
border: 1px solid #ddd !important;
box-shadow: none !important;
page-break-inside: avoid;
}
.card-header {
background: #f5f5f5 !important;
border-bottom: 1px solid #ddd !important;
}
/* Table styling for print */
.table {
font-size: 10px !important;
}
.table th {
background: #333 !important;
color: white !important;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
/* Ensure badges are visible */
.badge {
border: 1px solid currentColor;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
/* Page breaks */
.card {
page-break-inside: avoid;
}
tr {
page-break-inside: avoid;
}
}
</style>
{% endblock %}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment