Fix PDF export and add printable page formatting

parent 632709c8
...@@ -1910,87 +1910,159 @@ def report_detail(sync_id): ...@@ -1910,87 +1910,159 @@ def report_detail(sync_id):
def export_reports(query, export_format): def export_reports(query, export_format):
"""Export reports to various formats""" """Export reports to various formats"""
try: try:
from app.models import ReportSync, Bet, ExtractionStats from app.models import ReportSync, Bet, ExtractionStats, APIToken, ClientActivity, MatchReport
from sqlalchemy import func, and_, or_
# Get filter parameters from request
client_id_filter = request.args.get('client_id', '').strip()
date_range_filter = request.args.get('date_range', 'today').strip()
start_date_filter = request.args.get('start_date', '').strip()
end_date_filter = request.args.get('end_date', '').strip()
start_time_filter = request.args.get('start_time', '').strip()
end_time_filter = request.args.get('end_time', '').strip()
timezone_filter = request.args.get('timezone', 'auto').strip()
# Calculate date range based on filter
now = datetime.utcnow()
start_date = None
end_date = None
if date_range_filter == 'today':
start_date = now.replace(hour=0, minute=0, second=0, microsecond=0)
end_date = now
elif date_range_filter == 'yesterday':
yesterday = now - timedelta(days=1)
start_date = yesterday.replace(hour=0, minute=0, second=0, microsecond=0)
end_date = yesterday.replace(hour=23, minute=59, second=59, microsecond=999999)
elif date_range_filter == 'this_week':
start_date = now - timedelta(days=now.weekday())
start_date = start_date.replace(hour=0, minute=0, second=0, microsecond=0)
end_date = now
elif date_range_filter == 'last_week':
last_week_end = now - timedelta(days=now.weekday() + 1)
last_week_start = last_week_end - timedelta(days=6)
start_date = last_week_start.replace(hour=0, minute=0, second=0, microsecond=0)
end_date = last_week_end.replace(hour=23, minute=59, second=59, microsecond=999999)
elif date_range_filter == 'this_month':
start_date = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
end_date = now
elif date_range_filter == 'all':
start_date = None
end_date = None
elif date_range_filter == 'custom':
if start_date_filter:
try:
start_date = datetime.strptime(start_date_filter, '%Y-%m-%d')
if start_time_filter:
hour, minute = map(int, start_time_filter.split(':'))
start_date = start_date.replace(hour=hour, minute=minute, second=0, microsecond=0)
else:
start_date = start_date.replace(hour=0, minute=0, second=0, microsecond=0)
except ValueError:
pass
if end_date_filter:
try:
end_date = datetime.strptime(end_date_filter, '%Y-%m-%d')
if end_time_filter:
hour, minute = map(int, end_time_filter.split(':'))
end_date = end_date.replace(hour=hour, minute=minute, second=59, microsecond=999999)
else:
end_date = end_date.replace(hour=23, minute=59, second=59, microsecond=999999)
except ValueError:
pass
# Build query for MatchReport data (same as reports page)
base_query = MatchReport.query
# Apply user filter
if not current_user.is_admin:
user_token_ids = [t.id for t in APIToken.query.filter_by(user_id=current_user.id).all()]
if user_token_ids:
client_ids = [c.rustdesk_id for c in ClientActivity.query.filter(
ClientActivity.api_token_id.in_(user_token_ids)
).all()]
if client_ids:
base_query = base_query.filter(MatchReport.client_id.in_(client_ids))
else:
base_query = base_query.filter(MatchReport.client_id == 'none')
else:
base_query = base_query.filter(MatchReport.client_id == 'none')
# Apply filters
if client_id_filter:
base_query = base_query.filter(MatchReport.client_id == client_id_filter)
# Get all reports matching query if start_date:
reports = query.all() base_query = base_query.filter(MatchReport.match_datetime >= start_date)
if not reports: if end_date:
base_query = base_query.filter(MatchReport.match_datetime <= end_date)
# Get all matching match reports for aggregation
matching_reports = base_query.all()
if not matching_reports:
flash('No reports to export', 'warning') flash('No reports to export', 'warning')
return redirect(url_for('main.reports')) return redirect(url_for('main.reports'))
# Aggregate by client
client_aggregates = {}
for report in matching_reports:
client_id = report.client_id
if client_id not in client_aggregates:
client_activity = ClientActivity.query.filter_by(rustdesk_id=client_id).first()
token_name = client_activity.api_token.name if client_activity and client_activity.api_token else 'Unknown'
client_aggregates[client_id] = {
'client_id': client_id,
'token_name': token_name,
'total_payin': 0.0,
'total_payout': 0.0,
'total_bets': 0,
'total_matches': 0,
'winning_bets': 0,
'losing_bets': 0,
'pending_bets': 0,
'cap_balance': 0.0,
'accumulated_shortfall': 0.0,
'last_match_timestamp': report.match_datetime
}
client_aggregates[client_id]['total_payin'] += float(report.total_payin) if report.total_payin else 0.0
client_aggregates[client_id]['total_payout'] += float(report.total_payout) if report.total_payout else 0.0
client_aggregates[client_id]['total_bets'] += report.total_bets
client_aggregates[client_id]['total_matches'] += 1
client_aggregates[client_id]['winning_bets'] += report.winning_bets
client_aggregates[client_id]['losing_bets'] += report.losing_bets
client_aggregates[client_id]['pending_bets'] += report.pending_bets
if report.match_datetime >= client_aggregates[client_id]['last_match_timestamp']:
client_aggregates[client_id]['cap_balance'] = float(report.cap_compensation_balance) if report.cap_compensation_balance else 0.0
client_aggregates[client_id]['accumulated_shortfall'] = float(report.accumulated_shortfall) if report.accumulated_shortfall else 0.0
client_aggregates[client_id]['last_match_timestamp'] = report.match_datetime
# Calculate balance for each client
for client_id, data in client_aggregates.items():
data['balance'] = data['total_payin'] - data['total_payout']
# Prepare data for export # Prepare data for export
export_data = [] export_data = []
for report in reports: for client_id, data in client_aggregates.items():
# Get bets and stats for this report
bets = Bet.query.filter_by(sync_id=report.id).all()
stats = ExtractionStats.query.filter_by(sync_id=report.id).all()
# Add summary row
export_data.append({ export_data.append({
'Type': 'Summary', 'Client Name': data['token_name'],
'Sync ID': report.sync_id, 'Client ID': data['client_id'],
'Client ID': report.client_id, 'Total Matches': data['total_matches'],
'Sync Timestamp': report.sync_timestamp.strftime('%Y-%m-%d %H:%M:%S') if report.sync_timestamp else '', 'Total Bets': data['total_bets'],
'Date Range': report.date_range, 'Winning Bets': data['winning_bets'],
'Start Date': report.start_date.strftime('%Y-%m-%d') if report.start_date else '', 'Losing Bets': data['losing_bets'],
'End Date': report.end_date.strftime('%Y-%m-%d') if report.end_date else '', 'Pending Bets': data['pending_bets'],
'Total Payin': float(report.total_payin) if report.total_payin else 0.0, 'Total Payin': data['total_payin'],
'Total Payout': float(report.total_payout) if report.total_payout else 0.0, 'Total Payout': data['total_payout'],
'Net Profit': float(report.net_profit) if report.net_profit else 0.0, 'Balance': data['balance'],
'Total Bets': report.total_bets, 'CAP Redistribution': data['cap_balance'],
'Total Matches': report.total_matches, 'Accumulated Shortfall': data['accumulated_shortfall'],
'Bet UUID': '', 'Last Match': data['last_match_timestamp'].strftime('%Y-%m-%d %H:%M:%S') if data['last_match_timestamp'] else ''
'Match ID': '',
'Outcome': '',
'Amount': '',
'Result': ''
}) })
# Add bet details
for bet in bets:
for detail in bet.details:
export_data.append({
'Type': 'Bet Detail',
'Sync ID': report.sync_id,
'Client ID': report.client_id,
'Sync Timestamp': report.sync_timestamp.strftime('%Y-%m-%d %H:%M:%S') if report.sync_timestamp else '',
'Date Range': report.date_range,
'Start Date': report.start_date.strftime('%Y-%m-%d') if report.start_date else '',
'End Date': report.end_date.strftime('%Y-%m-%d') if report.end_date else '',
'Total Payin': '',
'Total Payout': '',
'Net Profit': '',
'Total Bets': '',
'Total Matches': '',
'Bet UUID': bet.uuid,
'Match ID': detail.match_id,
'Outcome': detail.outcome,
'Amount': float(detail.amount) if detail.amount else 0.0,
'Result': detail.result
})
# Add extraction stats
for stat in stats:
export_data.append({
'Type': 'Extraction Stats',
'Sync ID': report.sync_id,
'Client ID': report.client_id,
'Sync Timestamp': report.sync_timestamp.strftime('%Y-%m-%d %H:%M:%S') if report.sync_timestamp else '',
'Date Range': report.date_range,
'Start Date': report.start_date.strftime('%Y-%m-%d') if report.start_date else '',
'End Date': report.end_date.strftime('%Y-%m-%d') if report.end_date else '',
'Total Payin': float(stat.total_amount_collected) if stat.total_amount_collected else 0.0,
'Total Payout': float(stat.total_redistributed) if stat.total_redistributed else 0.0,
'Net Profit': '',
'Total Bets': stat.total_bets,
'Total Matches': '',
'Bet UUID': '',
'Match ID': stat.match_id,
'Outcome': stat.actual_result,
'Amount': '',
'Result': stat.extraction_result
})
# Create DataFrame # Create DataFrame
df = pd.DataFrame(export_data) df = pd.DataFrame(export_data)
...@@ -2018,11 +2090,9 @@ def export_reports(query, export_format): ...@@ -2018,11 +2090,9 @@ def export_reports(query, export_format):
) )
elif export_format == 'pdf': elif export_format == 'pdf':
# For PDF, we'll create a simple HTML table and convert it
from weasyprint import HTML from weasyprint import HTML
import tempfile
# Create HTML table # Create HTML table with styling
html_table = df.to_html(index=False, classes='table table-striped table-bordered') html_table = df.to_html(index=False, classes='table table-striped table-bordered')
html_content = f""" html_content = f"""
<!DOCTYPE html> <!DOCTYPE html>
...@@ -2030,15 +2100,19 @@ def export_reports(query, export_format): ...@@ -2030,15 +2100,19 @@ def export_reports(query, export_format):
<head> <head>
<style> <style>
body {{ font-family: Arial, sans-serif; margin: 20px; }} body {{ font-family: Arial, sans-serif; margin: 20px; }}
h1 {{ color: #333; border-bottom: 2px solid #007bff; padding-bottom: 10px; }}
table {{ border-collapse: collapse; width: 100%; margin-bottom: 20px; }} table {{ border-collapse: collapse; width: 100%; margin-bottom: 20px; }}
th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }} th {{ background-color: #007bff; color: white; padding: 10px; text-align: left; font-weight: bold; }}
th {{ background-color: #4CAF50; color: white; }} td {{ padding: 8px; border: 1px solid #ddd; }}
tr:nth-child(even) {{ background-color: #f2f2f2; }} tr:nth-child(even) {{ background-color: #f9f9f9; }}
.text-right {{ text-align: right; }}
.text-center {{ text-align: center; }}
</style> </style>
</head> </head>
<body> <body>
<h1>Reports Export</h1> <h1>Reports Export</h1>
<p>Generated on: {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')}</p> <p>Generated on: {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')}</p>
<p>Total Clients: {len(export_data)}</p>
{html_table} {html_table}
</body> </body>
</html> </html>
......
...@@ -158,9 +158,96 @@ ...@@ -158,9 +158,96 @@
margin-top: 12px; margin-top: 12px;
} }
</style> </style>
/* Print button styles */
.print-btn {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
background: #007bff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
.print-btn:hover {
background: #0056b3;
}
/* Print styles */
@media print {
.print-btn,
.btn-group,
#filterCollapse,
.pagination,
.per-page-selector {
display: none !important;
}
.container {
max-width: 100% !important;
padding: 0 !important;
}
.card {
box-shadow: none !important;
border: 1px solid #ddd !important;
break-inside: avoid;
}
.card-header {
background: #f8f9fa !important;
color: #000 !important;
border-bottom: 1px solid #ddd !important;
}
.table {
font-size: 12px;
}
.table thead th {
background: #f8f9fa !important;
color: #000 !important;
font-weight: bold;
border: 1px solid #ddd !important;
position: static;
}
.table tbody td {
border: 1px solid #ddd !important;
}
.text-success {
color: #000 !important;
font-weight: bold;
}
.text-danger {
color: #000 !important;
font-weight: bold;
}
/* Avoid breaking rows across pages */
tr {
page-break-inside: avoid;
}
/* Add page breaks between sections */
.card {
margin-bottom: 20px;
}
}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<!-- Print button -->
<button class="print-btn" onclick="window.print()">
<i class="fas fa-print"></i> Print Page
</button>
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
......
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