Commit 0ca2b9bb authored by Your Name's avatar Your Name

Update 1.0.32

parent 2fd8202f
This diff is collapsed.
...@@ -557,9 +557,9 @@ class OverlayWebChannel(QObject): ...@@ -557,9 +557,9 @@ class OverlayWebChannel(QObject):
return [] return []
def _get_winning_outcomes_from_database(self, match_id: int) -> List[Dict[str, Any]]: def _get_winning_outcomes_from_database(self, match_id: int) -> List[Dict[str, Any]]:
"""Get winning outcomes aggregated by outcome type for a match from database, including UNDER/OVER when they win""" """Get settled winning outcomes aggregated by outcome type for a match."""
try: try:
from ..database.models import BetDetailModel, MatchModel, MatchOutcomeModel, ExtractionStatsModel, ExtractionAssociationModel from ..database.models import BetDetailModel, MatchModel
from sqlalchemy import func from sqlalchemy import func
logger.debug(f"QtWebChannel: _get_winning_outcomes_from_database called for match {match_id}") logger.debug(f"QtWebChannel: _get_winning_outcomes_from_database called for match {match_id}")
...@@ -575,45 +575,7 @@ class OverlayWebChannel(QObject): ...@@ -575,45 +575,7 @@ class OverlayWebChannel(QObject):
try: try:
logger.debug(f"QtWebChannel: Executing query for match {match_id}") logger.debug(f"QtWebChannel: Executing query for match {match_id}")
# First, check if match has result set (from extraction) settled_winning_outcomes_query = session.query(
match = session.query(MatchModel).filter(MatchModel.id == match_id).first()
if match and match.result:
logger.debug(f"QtWebChannel: Match {match_id} has result set: {match.result}")
# Get winning outcomes from associations
winning_outcomes_query = session.query(ExtractionAssociationModel.outcome_name).filter(
ExtractionAssociationModel.extraction_result == match.result
).all()
winning_outcome_names = [outcome.outcome_name for outcome in winning_outcomes_query]
logger.debug(f"QtWebChannel: Found {len(winning_outcome_names)} winning outcomes from associations for result {match.result}: {winning_outcome_names}")
# Include under_over_result if available
if match.under_over_result:
winning_outcome_names.append(match.under_over_result)
logger.debug(f"QtWebChannel: Added under_over_result: {match.under_over_result}")
# Calculate amounts from pending bets
outcomes_data = []
for outcome_name in set(winning_outcome_names):
total_amount = session.query(func.sum(BetDetailModel.amount)).filter(
BetDetailModel.match_id == match_id,
BetDetailModel.outcome == outcome_name,
BetDetailModel.result == 'pending'
).scalar() or 0.0
outcome_data = {
'outcome': outcome_name,
'amount': float(total_amount)
}
outcomes_data.append(outcome_data)
logger.debug(f"QtWebChannel: Calculated amount for {outcome_name}: {total_amount}")
logger.debug(f"QtWebChannel: Retrieved {len(outcomes_data)} winning outcomes from match result: {outcomes_data}")
return outcomes_data
# Fallback: try to get winning outcomes from updated bet results
all_winning_outcomes_query = session.query(
BetDetailModel.outcome, BetDetailModel.outcome,
func.sum(BetDetailModel.win_amount).label('total_amount') func.sum(BetDetailModel.win_amount).label('total_amount')
).join(MatchModel).filter( ).join(MatchModel).filter(
...@@ -622,75 +584,28 @@ class OverlayWebChannel(QObject): ...@@ -622,75 +584,28 @@ class OverlayWebChannel(QObject):
MatchModel.active_status == True MatchModel.active_status == True
).group_by(BetDetailModel.outcome).all() ).group_by(BetDetailModel.outcome).all()
logger.debug(f"QtWebChannel: Query returned {len(all_winning_outcomes_query)} winning outcomes from bet results") logger.debug(
f"QtWebChannel: Query returned {len(settled_winning_outcomes_query)} settled winning outcomes"
)
if all_winning_outcomes_query: if settled_winning_outcomes_query:
# Convert to dictionary format for JavaScript
outcomes_data = [] outcomes_data = []
for outcome_name, total_amount in all_winning_outcomes_query: for outcome_name, total_amount in settled_winning_outcomes_query:
outcome_data = { outcome_data = {
'outcome': outcome_name, 'outcome': outcome_name,
'amount': float(total_amount) if total_amount else 0.0 'amount': float(total_amount) if total_amount else 0.0
} }
outcomes_data.append(outcome_data) outcomes_data.append(outcome_data)
logger.debug(f"QtWebChannel: Retrieved {len(outcomes_data)} winning outcomes from bet results: {outcomes_data}") logger.debug(
f"QtWebChannel: Retrieved {len(outcomes_data)} settled winning outcomes: {outcomes_data}"
)
return outcomes_data return outcomes_data
# Fallback: No winning bets found, try to get from extraction results logger.debug(
logger.debug("QtWebChannel: No winning bets found, trying extraction results fallback") f"QtWebChannel: No settled winning outcomes found for match {match_id}"
)
# Get extraction stats for this match return []
extraction_stats = session.query(ExtractionStatsModel).filter(
ExtractionStatsModel.match_id == match_id
).first()
if not extraction_stats or not extraction_stats.actual_result:
logger.debug("QtWebChannel: No extraction stats found for match")
return []
extraction_result = extraction_stats.actual_result
logger.debug(f"QtWebChannel: Found extraction result: {extraction_result}")
# Get winning outcomes from extraction associations
winning_outcomes_query = session.query(ExtractionAssociationModel.outcome_name).filter(
ExtractionAssociationModel.extraction_result == extraction_result
).all()
winning_outcome_names = [outcome.outcome_name for outcome in winning_outcomes_query]
logger.debug(f"QtWebChannel: Found {len(winning_outcome_names)} winning outcomes from associations: {winning_outcome_names}")
# Get the match to check for under_over_result
match = session.query(MatchModel).filter(MatchModel.id == match_id).first()
under_over_result = match.under_over_result if match else None
logger.debug(f"QtWebChannel: Under/over result from match: {under_over_result}")
# Combine winning outcomes
all_winning_outcomes = set(winning_outcome_names)
if under_over_result:
all_winning_outcomes.add(under_over_result)
logger.debug(f"QtWebChannel: Combined winning outcomes: {all_winning_outcomes}")
# Calculate amounts for each winning outcome from pending bets
outcomes_data = []
for outcome_name in all_winning_outcomes:
# Get total amount of pending bets for this outcome
total_amount = session.query(func.sum(BetDetailModel.amount)).filter(
BetDetailModel.match_id == match_id,
BetDetailModel.outcome == outcome_name,
BetDetailModel.result == 'pending'
).scalar() or 0.0
outcome_data = {
'outcome': outcome_name,
'amount': float(total_amount)
}
outcomes_data.append(outcome_data)
logger.debug(f"QtWebChannel: Calculated amount for {outcome_name}: {total_amount}")
logger.debug(f"QtWebChannel: Retrieved {len(outcomes_data)} winning outcomes from extraction fallback: {outcomes_data}")
return outcomes_data
finally: finally:
session.close() session.close()
......
...@@ -281,7 +281,7 @@ ...@@ -281,7 +281,7 @@
</div> </div>
<hr class="my-2"> <hr class="my-2">
<div class="text-center"> <div class="text-center">
<strong class="text-success currency-amount" data-amount="{{ results.winnings|round(2) }}">Winnings: €{{ results.winnings|round(2) }}</strong> <strong class="text-success currency-amount" data-amount="{{ results.winnings|round(2) }}">Settled Winnings: €{{ results.winnings|round(2) }}</strong>
</div> </div>
</div> </div>
</div> </div>
...@@ -456,6 +456,7 @@ ...@@ -456,6 +456,7 @@
"amount": {{ detail.amount|round(2) }}, "amount": {{ detail.amount|round(2) }},
"odds": {{ detail.odds|round(2) }}, "odds": {{ detail.odds|round(2) }},
"potential_winning": {{ detail.potential_winning|round(2) }}, "potential_winning": {{ detail.potential_winning|round(2) }},
"settled_winning": {{ detail.settled_winning|round(2) }},
"result": "{{ detail.result }}" "result": "{{ detail.result }}"
}{% if not loop.last %},{% endif %} }{% if not loop.last %},{% endif %}
{% endfor %} {% endfor %}
...@@ -472,7 +473,7 @@ document.addEventListener('currencySettingsLoaded', function(event) { ...@@ -472,7 +473,7 @@ document.addEventListener('currencySettingsLoaded', function(event) {
document.querySelectorAll('.currency-amount').forEach(element => { document.querySelectorAll('.currency-amount').forEach(element => {
const amount = parseFloat(element.dataset.amount || 0); const amount = parseFloat(element.dataset.amount || 0);
const prefix = element.textContent.includes('Total:') ? 'Total: ' : const prefix = element.textContent.includes('Total:') ? 'Total: ' :
element.textContent.includes('Winnings:') ? 'Winnings: ' : ''; element.textContent.includes('Settled Winnings:') ? 'Settled Winnings: ' : '';
element.textContent = prefix + formatCurrency(amount); element.textContent = prefix + formatCurrency(amount);
}); });
}); });
...@@ -658,7 +659,7 @@ function generateReceiptHtml(betData) { ...@@ -658,7 +659,7 @@ function generateReceiptHtml(betData) {
</div> </div>
<div class="receipt-bet-line"> <div class="receipt-bet-line">
<span>ODDS: ${parseFloat(detail.odds).toFixed(2)}</span> <span>ODDS: ${parseFloat(detail.odds).toFixed(2)}</span>
<span>POTENTIAL WIN: ${formatCurrency(parseFloat(detail.potential_winning))}</span> <span>${detail.result === 'win' ? 'SETTLED WIN' : 'POTENTIAL WIN'}: ${formatCurrency(parseFloat(detail.result === 'win' ? detail.settled_winning : detail.potential_winning))}</span>
</div> </div>
<div class="receipt-status"> <div class="receipt-status">
STATUS: ${detail.result.toUpperCase()} STATUS: ${detail.result.toUpperCase()}
...@@ -1285,4 +1286,4 @@ function showNotification(message, type = 'info') { ...@@ -1285,4 +1286,4 @@ function showNotification(message, type = 'info') {
}, 3000); }, 3000);
} }
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -281,7 +281,7 @@ ...@@ -281,7 +281,7 @@
</div> </div>
<hr class="my-2"> <hr class="my-2">
<div class="text-center"> <div class="text-center">
<strong class="text-success currency-amount" data-amount="{{ results.winnings|round(2) }}">Winnings: €{{ results.winnings|round(2) }}</strong> <strong class="text-success currency-amount" data-amount="{{ results.winnings|round(2) }}">Settled Winnings: €{{ results.winnings|round(2) }}</strong>
</div> </div>
</div> </div>
</div> </div>
...@@ -458,6 +458,7 @@ ...@@ -458,6 +458,7 @@
"amount": {{ detail.amount|round(2) }}, "amount": {{ detail.amount|round(2) }},
"odds": {{ detail.odds|round(2) }}, "odds": {{ detail.odds|round(2) }},
"potential_winning": {{ detail.potential_winning|round(2) }}, "potential_winning": {{ detail.potential_winning|round(2) }},
"settled_winning": {{ detail.settled_winning|round(2) }},
"result": "{{ detail.result }}" "result": "{{ detail.result }}"
}{% if not loop.last %},{% endif %} }{% if not loop.last %},{% endif %}
{% endfor %} {% endfor %}
...@@ -474,7 +475,7 @@ document.addEventListener('currencySettingsLoaded', function(event) { ...@@ -474,7 +475,7 @@ document.addEventListener('currencySettingsLoaded', function(event) {
document.querySelectorAll('.currency-amount').forEach(element => { document.querySelectorAll('.currency-amount').forEach(element => {
const amount = parseFloat(element.dataset.amount || 0); const amount = parseFloat(element.dataset.amount || 0);
const prefix = element.textContent.includes('Total:') ? 'Total: ' : const prefix = element.textContent.includes('Total:') ? 'Total: ' :
element.textContent.includes('Winnings:') ? 'Winnings: ' : ''; element.textContent.includes('Settled Winnings:') ? 'Settled Winnings: ' : '';
element.textContent = prefix + formatCurrency(amount); element.textContent = prefix + formatCurrency(amount);
}); });
}); });
...@@ -717,7 +718,7 @@ function generateReceiptHtml(betData) { ...@@ -717,7 +718,7 @@ function generateReceiptHtml(betData) {
</div> </div>
<div class="receipt-bet-line"> <div class="receipt-bet-line">
<span>ODDS: ${parseFloat(detail.odds).toFixed(2)}</span> <span>ODDS: ${parseFloat(detail.odds).toFixed(2)}</span>
<span>POTENTIAL WIN: ${formatCurrency(parseFloat(detail.potential_winning))}</span> <span>${detail.result === 'win' ? 'SETTLED WIN' : 'POTENTIAL WIN'}: ${formatCurrency(parseFloat(detail.result === 'win' ? detail.settled_winning : detail.potential_winning))}</span>
</div> </div>
<div class="receipt-status"> <div class="receipt-status">
STATUS: ${detail.result.toUpperCase()} STATUS: ${detail.result.toUpperCase()}
...@@ -1304,4 +1305,4 @@ function showNotification(message, type = 'info') { ...@@ -1304,4 +1305,4 @@ function showNotification(message, type = 'info') {
}, 3000); }, 3000);
} }
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -661,7 +661,7 @@ function displayBetDetails(bet) { ...@@ -661,7 +661,7 @@ function displayBetDetails(bet) {
${bet.results.winnings > 0 ? ` ${bet.results.winnings > 0 ? `
<div class="alert alert-success mt-3"> <div class="alert alert-success mt-3">
<i class="fas fa-trophy me-2"></i> <i class="fas fa-trophy me-2"></i>
<strong>Potential Winnings: <span class="currency-amount" data-amount="${bet.results.winnings}">${formatCurrency(bet.results.winnings)}</span></strong> <strong>Settled Winnings: <span class="currency-amount" data-amount="${bet.results.winnings}">${formatCurrency(bet.results.winnings)}</span></strong>
</div> </div>
` : ''} ` : ''}
...@@ -758,4 +758,4 @@ function copyToClipboard(elementId) { ...@@ -758,4 +758,4 @@ function copyToClipboard(elementId) {
}, 2000); }, 2000);
} }
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -685,7 +685,7 @@ function displayBetDetails(bet) { ...@@ -685,7 +685,7 @@ function displayBetDetails(bet) {
${bet.results.winnings > 0 ? ` ${bet.results.winnings > 0 ? `
<div class="alert alert-success mt-3"> <div class="alert alert-success mt-3">
<i class="fas fa-trophy me-2"></i> <i class="fas fa-trophy me-2"></i>
<strong>Potential Winnings: <span class="currency-amount" data-amount="${bet.results.winnings}">${formatCurrency(bet.results.winnings)}</span></strong> <strong>Settled Winnings: <span class="currency-amount" data-amount="${bet.results.winnings}">${formatCurrency(bet.results.winnings)}</span></strong>
</div> </div>
` : ''} ` : ''}
...@@ -782,4 +782,4 @@ function copyToClipboard(elementId) { ...@@ -782,4 +782,4 @@ function copyToClipboard(elementId) {
}, 2000); }, 2000);
} }
</script> </script>
{% endblock %} {% endblock %}
\ No newline at end of file
# MbetterClient v1.0.32
Cross-platform multimedia client application
## Installation
1. Extract this package to your desired location
2. Run the executable file
3. The application will create necessary configuration files on first run
## System Requirements
- **Operating System**: Linux 6.12.15-amd64
- **Architecture**: x86_64
- **Memory**: 512 MB RAM minimum, 1 GB recommended
- **Disk Space**: 100 MB free space
## Configuration
The application stores its configuration and database in:
- **Windows**: `%APPDATA%\MbetterClient`
- **macOS**: `~/Library/Application Support/MbetterClient`
- **Linux**: `~/.config/MbetterClient`
## Web Interface
By default, the web interface is available at: http://localhost:5001
Default login credentials:
- Username: admin
- Password: admin
**Please change the default password after first login.**
## Support
For support and documentation, please visit: https://git.nexlab.net/mbetter/mbetterc
## Version Information
- Version: 1.0.32
- Build Date: sissy
- Platform: Linux-6.12.15-amd64-x86_64-with-glibc2.42
...@@ -12,6 +12,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) ...@@ -12,6 +12,7 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from mbetterclient.web_dashboard import routes as routes_module from mbetterclient.web_dashboard import routes as routes_module
from mbetterclient.web_dashboard.routes import api_bp, is_bet_detail_winning, is_settled_bet_detail_winning from mbetterclient.web_dashboard.routes import api_bp, is_bet_detail_winning, is_settled_bet_detail_winning
from mbetterclient.qt_player.player import OverlayWebChannel
class DummyDetail: class DummyDetail:
...@@ -378,3 +379,84 @@ def test_match_details_includes_settled_winning_field(monkeypatch, isolated_api_ ...@@ -378,3 +379,84 @@ def test_match_details_includes_settled_winning_field(monkeypatch, isolated_api_
assert response.status_code == 200 assert response.status_code == 200
payload = response.get_json() payload = response.get_json()
assert payload['total'] == 0 assert payload['total'] == 0
def test_report_templates_label_settled_winnings():
from pathlib import Path
bet_details_template = Path('/working/mbetterc/mbetterclient/web_dashboard/templates/dashboard/bet_details.html').read_text()
admin_bet_details_template = Path('/working/mbetterc/mbetterclient/web_dashboard/templates/dashboard/admin_bet_details.html').read_text()
verify_template = Path('/working/mbetterc/mbetterclient/web_dashboard/templates/dashboard/verify_bet.html').read_text()
cashier_verify_template = Path('/working/mbetterc/mbetterclient/web_dashboard/templates/dashboard/cashier_verify_bet.html').read_text()
assert 'Settled Winnings' in bet_details_template
assert 'Settled Winnings' in admin_bet_details_template
assert 'Settled Winnings' in verify_template
assert 'Settled Winnings' in cashier_verify_template
def test_qt_overlay_winning_outcomes_use_settled_rows():
class AggregateQueryStub:
def __init__(self, rows):
self.rows = rows
def join(self, *args, **kwargs):
return self
def filter(self, *args, **kwargs):
return self
def group_by(self, *args, **kwargs):
return self
def all(self):
return self.rows
class OverlaySessionStub:
def __init__(self, rows):
self.rows = rows
def query(self, *models):
if len(models) == 2:
return AggregateQueryStub(self.rows)
return QueryStub([])
def close(self):
return None
overlay = OverlayWebChannel(
db_manager=DummyDbManager(OverlaySessionStub([('WIN1', 250.0), ('UNDER', 175.0)]))
)
assert overlay._get_winning_outcomes_from_database(10) == [
{'outcome': 'WIN1', 'amount': 250.0},
{'outcome': 'UNDER', 'amount': 175.0},
]
def test_qt_overlay_winning_outcomes_return_empty_without_settled_wins():
class AggregateQueryStub:
def join(self, *args, **kwargs):
return self
def filter(self, *args, **kwargs):
return self
def group_by(self, *args, **kwargs):
return self
def all(self):
return []
class OverlaySessionStub:
def query(self, *models):
if len(models) == 2:
return AggregateQueryStub()
return QueryStub([])
def close(self):
return None
overlay = OverlayWebChannel(db_manager=DummyDbManager(OverlaySessionStub()))
assert overlay._get_winning_outcomes_from_database(10) == []
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