Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
M
MBetterd
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Mbetter
MBetterd
Commits
58f3762b
Commit
58f3762b
authored
Feb 03, 2026
by
Stefy Lanza (nextime / spora )
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add export functionality to all reports pages
parent
849b646a
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
618 additions
and
9 deletions
+618
-9
routes.py
app/main/routes.py
+573
-0
bet_detail.html
app/templates/main/bet_detail.html
+15
-3
client_report_detail.html
app/templates/main/client_report_detail.html
+15
-3
match_report_detail.html
app/templates/main/match_report_detail.html
+15
-3
No files found.
app/main/routes.py
View file @
58f3762b
...
@@ -2265,6 +2265,564 @@ def sync_logs():
...
@@ -2265,6 +2265,564 @@ def sync_logs():
flash
(
'Error loading sync logs'
,
'error'
)
flash
(
'Error loading sync logs'
,
'error'
)
return
render_template
(
'main/sync_logs.html'
,
logs
=
[],
pagination
=
None
,
client_ids
=
[])
return
render_template
(
'main/sync_logs.html'
,
logs
=
[],
pagination
=
None
,
client_ids
=
[])
def
export_bet_detail
(
client_id
,
match_id
,
bet_uuid
,
export_format
):
"""Export bet detail to CSV, XLSX, or PDF"""
try
:
from
app.models
import
Bet
,
BetDetail
,
APIToken
,
ClientActivity
# Check if user has access to this client
if
current_user
.
is_admin
:
bet_query
=
Bet
.
query
.
filter_by
(
client_id
=
client_id
,
match_id
=
match_id
,
uuid
=
bet_uuid
)
else
:
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_id
not
in
client_ids
:
flash
(
'Access denied to this client'
,
'error'
)
return
redirect
(
url_for
(
'main.reports'
))
bet_query
=
Bet
.
query
.
filter_by
(
client_id
=
client_id
,
match_id
=
match_id
,
uuid
=
bet_uuid
)
else
:
flash
(
'Access denied to this client'
,
'error'
)
return
redirect
(
url_for
(
'main.reports'
))
# Get bet and bet details
bet
=
bet_query
.
first
()
if
not
bet
:
flash
(
'Bet not found'
,
'error'
)
return
redirect
(
url_for
(
'main.match_report_detail'
,
client_id
=
client_id
,
match_id
=
match_id
))
bet_details
=
BetDetail
.
query
.
filter_by
(
bet_id
=
bet
.
id
)
.
all
()
# Prepare data for export
export_data
=
[]
# Add bet summary
export_data
.
append
({
'Type'
:
'Bet Summary'
,
'Bet UUID'
:
bet
.
uuid
,
'Match ID'
:
match_id
,
'Match Number'
:
bet
.
match_number
,
'Bet DateTime'
:
bet
.
bet_datetime
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
)
if
bet
.
bet_datetime
else
''
,
'Total Amount'
:
float
(
bet
.
total_amount
)
if
bet
.
total_amount
else
0.0
,
'Bet Count'
:
bet
.
bet_count
,
'Paid'
:
bet
.
paid
,
'Paid Out'
:
bet
.
paid_out
})
# Add bet details
for
detail
in
bet_details
:
export_data
.
append
({
'Type'
:
'Bet Detail'
,
'Bet UUID'
:
bet
.
uuid
,
'Match ID'
:
match_id
,
'Match Number'
:
bet
.
match_number
,
'Bet DateTime'
:
bet
.
bet_datetime
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
)
if
bet
.
bet_datetime
else
''
,
'Total Amount'
:
''
,
'Bet Count'
:
''
,
'Paid'
:
''
,
'Paid Out'
:
''
,
'Outcome'
:
detail
.
outcome
,
'Amount'
:
float
(
detail
.
amount
)
if
detail
.
amount
else
0.0
,
'Win Amount'
:
float
(
detail
.
win_amount
)
if
detail
.
win_amount
else
0.0
,
'Result'
:
detail
.
result
})
# Create DataFrame
df
=
pd
.
DataFrame
(
export_data
)
# Export based on format
if
export_format
==
'csv'
:
output
=
StringIO
()
df
.
to_csv
(
output
,
index
=
False
)
output
.
seek
(
0
)
return
Response
(
output
.
getvalue
(),
mimetype
=
'text/csv'
,
headers
=
{
'Content-Disposition'
:
'attachment; filename=bet_detail_export.csv'
}
)
elif
export_format
==
'xlsx'
:
output
=
BytesIO
()
with
pd
.
ExcelWriter
(
output
,
engine
=
'openpyxl'
)
as
writer
:
df
.
to_excel
(
writer
,
index
=
False
,
sheet_name
=
'Bet Detail'
)
output
.
seek
(
0
)
return
Response
(
output
.
getvalue
(),
mimetype
=
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
,
headers
=
{
'Content-Disposition'
:
'attachment; filename=bet_detail_export.xlsx'
}
)
elif
export_format
==
'pdf'
:
from
weasyprint
import
HTML
# Create HTML table with styling
html_table
=
df
.
to_html
(
index
=
False
,
classes
=
'table table-striped table-bordered'
)
html_content
=
f
"""
<!DOCTYPE html>
<html>
<head>
<style>
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; }}
th {{ background-color: #007bff; color: white; padding: 10px; text-align: left; font-weight: bold; }}
td {{ padding: 8px; border: 1px solid #ddd; }}
tr:nth-child(even) {{ background-color: #f9f9f9; }}
.text-right {{ text-align: right; }}
.text-center {{ text-align: center; }}
</style>
</head>
<body>
<h1>Bet Detail Export</h1>
<p>Generated on: {datetime.utcnow().strftime('
%
Y-
%
m-
%
d
%
H:
%
M:
%
S')}</p>
<p>Client ID: {client_id}</p>
<p>Match ID: {match_id}</p>
<p>Bet UUID: {bet_uuid}</p>
<p>Total Bet Details: {len(bet_details)}</p>
{html_table}
</body>
</html>
"""
# Generate PDF
pdf_bytes
=
HTML
(
string
=
html_content
)
.
write_pdf
()
return
Response
(
pdf_bytes
,
mimetype
=
'application/pdf'
,
headers
=
{
'Content-Disposition'
:
'attachment; filename=bet_detail_export.pdf'
}
)
else
:
flash
(
'Invalid export format'
,
'error'
)
return
redirect
(
url_for
(
'main.bet_detail'
,
client_id
=
client_id
,
match_id
=
match_id
,
bet_uuid
=
bet_uuid
))
except
Exception
as
e
:
logger
.
error
(
f
"Export bet detail error: {str(e)}"
)
flash
(
'Error exporting bet detail'
,
'error'
)
return
redirect
(
url_for
(
'main.bet_detail'
,
client_id
=
client_id
,
match_id
=
match_id
,
bet_uuid
=
bet_uuid
))
def
export_match_report
(
client_id
,
match_id
,
export_format
):
"""Export match report to CSV, XLSX, or PDF"""
try
:
from
app.models
import
MatchReport
,
Bet
,
BetDetail
,
APIToken
,
ClientActivity
from
datetime
import
timedelta
# Get filter parameters
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
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
# Check if user has access to this client
if
current_user
.
is_admin
:
match_report_query
=
MatchReport
.
query
.
filter_by
(
client_id
=
client_id
,
match_id
=
match_id
)
bets_query
=
Bet
.
query
.
filter_by
(
client_id
=
client_id
,
match_id
=
match_id
)
else
:
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_id
not
in
client_ids
:
flash
(
'Access denied to this client'
,
'error'
)
return
redirect
(
url_for
(
'main.reports'
))
match_report_query
=
MatchReport
.
query
.
filter_by
(
client_id
=
client_id
,
match_id
=
match_id
)
bets_query
=
Bet
.
query
.
filter_by
(
client_id
=
client_id
,
match_id
=
match_id
)
else
:
flash
(
'Access denied to this client'
,
'error'
)
return
redirect
(
url_for
(
'main.reports'
))
# Get match report (don't apply date filters - we want specific match)
match_report
=
match_report_query
.
first
()
if
not
match_report
:
flash
(
'Match report not found'
,
'error'
)
return
redirect
(
url_for
(
'main.client_report_detail'
,
client_id
=
client_id
))
# Apply date filters to bets
if
start_date
:
bets_query
=
bets_query
.
filter
(
Bet
.
bet_datetime
>=
start_date
)
if
end_date
:
bets_query
=
bets_query
.
filter
(
Bet
.
bet_datetime
<=
end_date
)
# Get bets and bet details
bets
=
bets_query
.
all
()
# Prepare data for export
export_data
=
[]
# Add match summary
export_data
.
append
({
'Type'
:
'Match Summary'
,
'Match ID'
:
match_report
.
match_id
,
'Match Number'
:
match_report
.
match_number
,
'Fixture ID'
:
match_report
.
fixture_id
,
'Match DateTime'
:
match_report
.
match_datetime
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
)
if
match_report
.
match_datetime
else
''
,
'Total Bets'
:
match_report
.
total_bets
,
'Winning Bets'
:
match_report
.
winning_bets
,
'Losing Bets'
:
match_report
.
losing_bets
,
'Pending Bets'
:
match_report
.
pending_bets
,
'Total Payin'
:
float
(
match_report
.
total_payin
)
if
match_report
.
total_payin
else
0.0
,
'Total Payout'
:
float
(
match_report
.
total_payout
)
if
match_report
.
total_payout
else
0.0
,
'Balance'
:
float
(
match_report
.
balance
)
if
match_report
.
balance
else
0.0
,
'Actual Result'
:
match_report
.
actual_result
,
'Extraction Result'
:
match_report
.
extraction_result
,
'CAP Applied'
:
match_report
.
cap_applied
,
'CAP Percentage'
:
match_report
.
cap_percentage
,
'CAP Balance'
:
float
(
match_report
.
cap_compensation_balance
)
if
match_report
.
cap_compensation_balance
else
0.0
,
'Accumulated Shortfall'
:
float
(
match_report
.
accumulated_shortfall
)
if
match_report
.
accumulated_shortfall
else
0.0
})
# Add bets
for
bet
in
bets
:
bet_details
=
BetDetail
.
query
.
filter_by
(
bet_id
=
bet
.
id
)
.
all
()
for
detail
in
bet_details
:
export_data
.
append
({
'Type'
:
'Bet Detail'
,
'Match ID'
:
match_report
.
match_id
,
'Match Number'
:
match_report
.
match_number
,
'Fixture ID'
:
match_report
.
fixture_id
,
'Match DateTime'
:
match_report
.
match_datetime
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
)
if
match_report
.
match_datetime
else
''
,
'Total Bets'
:
''
,
'Winning Bets'
:
''
,
'Losing Bets'
:
''
,
'Pending Bets'
:
''
,
'Total Payin'
:
''
,
'Total Payout'
:
''
,
'Balance'
:
''
,
'Actual Result'
:
''
,
'Extraction Result'
:
''
,
'CAP Applied'
:
''
,
'CAP Percentage'
:
''
,
'CAP Balance'
:
''
,
'Accumulated Shortfall'
:
''
,
'Bet UUID'
:
bet
.
uuid
,
'Bet DateTime'
:
bet
.
bet_datetime
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
)
if
bet
.
bet_datetime
else
''
,
'Total Amount'
:
float
(
bet
.
total_amount
)
if
bet
.
total_amount
else
0.0
,
'Bet Count'
:
bet
.
bet_count
,
'Paid'
:
bet
.
paid
,
'Paid Out'
:
bet
.
paid_out
,
'Outcome'
:
detail
.
outcome
,
'Amount'
:
float
(
detail
.
amount
)
if
detail
.
amount
else
0.0
,
'Win Amount'
:
float
(
detail
.
win_amount
)
if
detail
.
win_amount
else
0.0
,
'Result'
:
detail
.
result
})
# Create DataFrame
df
=
pd
.
DataFrame
(
export_data
)
# Export based on format
if
export_format
==
'csv'
:
output
=
StringIO
()
df
.
to_csv
(
output
,
index
=
False
)
output
.
seek
(
0
)
return
Response
(
output
.
getvalue
(),
mimetype
=
'text/csv'
,
headers
=
{
'Content-Disposition'
:
'attachment; filename=match_report_export.csv'
}
)
elif
export_format
==
'xlsx'
:
output
=
BytesIO
()
with
pd
.
ExcelWriter
(
output
,
engine
=
'openpyxl'
)
as
writer
:
df
.
to_excel
(
writer
,
index
=
False
,
sheet_name
=
'Match Report'
)
output
.
seek
(
0
)
return
Response
(
output
.
getvalue
(),
mimetype
=
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
,
headers
=
{
'Content-Disposition'
:
'attachment; filename=match_report_export.xlsx'
}
)
elif
export_format
==
'pdf'
:
from
weasyprint
import
HTML
# Create HTML table with styling
html_table
=
df
.
to_html
(
index
=
False
,
classes
=
'table table-striped table-bordered'
)
html_content
=
f
"""
<!DOCTYPE html>
<html>
<head>
<style>
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; }}
th {{ background-color: #007bff; color: white; padding: 10px; text-align: left; font-weight: bold; }}
td {{ padding: 8px; border: 1px solid #ddd; }}
tr:nth-child(even) {{ background-color: #f9f9f9; }}
.text-right {{ text-align: right; }}
.text-center {{ text-align: center; }}
</style>
</head>
<body>
<h1>Match Report Export</h1>
<p>Generated on: {datetime.utcnow().strftime('
%
Y-
%
m-
%
d
%
H:
%
M:
%
S')}</p>
<p>Client ID: {client_id}</p>
<p>Match ID: {match_id}</p>
<p>Total Bets: {len(bets)}</p>
{html_table}
</body>
</html>
"""
# Generate PDF
pdf_bytes
=
HTML
(
string
=
html_content
)
.
write_pdf
()
return
Response
(
pdf_bytes
,
mimetype
=
'application/pdf'
,
headers
=
{
'Content-Disposition'
:
'attachment; filename=match_report_export.pdf'
}
)
else
:
flash
(
'Invalid export format'
,
'error'
)
return
redirect
(
url_for
(
'main.match_report_detail'
,
client_id
=
client_id
,
match_id
=
match_id
))
except
Exception
as
e
:
logger
.
error
(
f
"Export match report error: {str(e)}"
)
flash
(
'Error exporting match report'
,
'error'
)
return
redirect
(
url_for
(
'main.match_report_detail'
,
client_id
=
client_id
,
match_id
=
match_id
))
def
export_client_report
(
client_id
,
export_format
):
"""Export client report to CSV, XLSX, or PDF"""
try
:
from
app.models
import
ReportSync
,
Bet
,
ExtractionStats
,
APIToken
,
ClientActivity
,
MatchReport
from
datetime
import
timedelta
# Get filter parameters
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
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
# Check if user has access to this client
if
current_user
.
is_admin
:
query
=
MatchReport
.
query
.
filter_by
(
client_id
=
client_id
)
else
:
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_id
not
in
client_ids
:
flash
(
'Access denied to this client'
,
'error'
)
return
redirect
(
url_for
(
'main.reports'
))
query
=
MatchReport
.
query
.
filter_by
(
client_id
=
client_id
)
else
:
flash
(
'Access denied to this client'
,
'error'
)
return
redirect
(
url_for
(
'main.reports'
))
# Apply date filters
if
start_date
:
query
=
query
.
filter
(
MatchReport
.
match_datetime
>=
start_date
)
if
end_date
:
query
=
query
.
filter
(
MatchReport
.
match_datetime
<=
end_date
)
# Get all matching match reports for this client
match_reports
=
query
.
all
()
if
not
match_reports
:
flash
(
'No reports to export'
,
'warning'
)
return
redirect
(
url_for
(
'main.client_report_detail'
,
client_id
=
client_id
))
# Prepare data for export
export_data
=
[]
for
report
in
match_reports
:
export_data
.
append
({
'Match ID'
:
report
.
match_id
,
'Match Number'
:
report
.
match_number
,
'Fixture ID'
:
report
.
fixture_id
,
'Match DateTime'
:
report
.
match_datetime
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
)
if
report
.
match_datetime
else
''
,
'Total Bets'
:
report
.
total_bets
,
'Winning Bets'
:
report
.
winning_bets
,
'Losing Bets'
:
report
.
losing_bets
,
'Pending Bets'
:
report
.
pending_bets
,
'Total Payin'
:
float
(
report
.
total_payin
)
if
report
.
total_payin
else
0.0
,
'Total Payout'
:
float
(
report
.
total_payout
)
if
report
.
total_payout
else
0.0
,
'Balance'
:
float
(
report
.
balance
)
if
report
.
balance
else
0.0
,
'Actual Result'
:
report
.
actual_result
,
'Extraction Result'
:
report
.
extraction_result
,
'CAP Applied'
:
report
.
cap_applied
,
'CAP Percentage'
:
report
.
cap_percentage
,
'CAP Balance'
:
float
(
report
.
cap_compensation_balance
)
if
report
.
cap_compensation_balance
else
0.0
,
'Accumulated Shortfall'
:
float
(
report
.
accumulated_shortfall
)
if
report
.
accumulated_shortfall
else
0.0
})
# Create DataFrame
df
=
pd
.
DataFrame
(
export_data
)
# Export based on format
if
export_format
==
'csv'
:
output
=
StringIO
()
df
.
to_csv
(
output
,
index
=
False
)
output
.
seek
(
0
)
return
Response
(
output
.
getvalue
(),
mimetype
=
'text/csv'
,
headers
=
{
'Content-Disposition'
:
'attachment; filename=client_report_export.csv'
}
)
elif
export_format
==
'xlsx'
:
output
=
BytesIO
()
with
pd
.
ExcelWriter
(
output
,
engine
=
'openpyxl'
)
as
writer
:
df
.
to_excel
(
writer
,
index
=
False
,
sheet_name
=
'Client Report'
)
output
.
seek
(
0
)
return
Response
(
output
.
getvalue
(),
mimetype
=
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
,
headers
=
{
'Content-Disposition'
:
'attachment; filename=client_report_export.xlsx'
}
)
elif
export_format
==
'pdf'
:
from
weasyprint
import
HTML
# Create HTML table with styling
html_table
=
df
.
to_html
(
index
=
False
,
classes
=
'table table-striped table-bordered'
)
html_content
=
f
"""
<!DOCTYPE html>
<html>
<head>
<style>
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; }}
th {{ background-color: #007bff; color: white; padding: 10px; text-align: left; font-weight: bold; }}
td {{ padding: 8px; border: 1px solid #ddd; }}
tr:nth-child(even) {{ background-color: #f9f9f9; }}
.text-right {{ text-align: right; }}
.text-center {{ text-align: center; }}
</style>
</head>
<body>
<h1>Client Report Export</h1>
<p>Generated on: {datetime.utcnow().strftime('
%
Y-
%
m-
%
d
%
H:
%
M:
%
S')}</p>
<p>Client ID: {client_id}</p>
<p>Total Matches: {len(export_data)}</p>
{html_table}
</body>
</html>
"""
# Generate PDF
pdf_bytes
=
HTML
(
string
=
html_content
)
.
write_pdf
()
return
Response
(
pdf_bytes
,
mimetype
=
'application/pdf'
,
headers
=
{
'Content-Disposition'
:
'attachment; filename=client_report_export.pdf'
}
)
else
:
flash
(
'Invalid export format'
,
'error'
)
return
redirect
(
url_for
(
'main.client_report_detail'
,
client_id
=
client_id
))
except
Exception
as
e
:
logger
.
error
(
f
"Export client report error: {str(e)}"
)
flash
(
'Error exporting client report'
,
'error'
)
return
redirect
(
url_for
(
'main.client_report_detail'
,
client_id
=
client_id
))
def
export_sync_logs
(
export_format
):
def
export_sync_logs
(
export_format
):
"""Export sync logs to CSV, XLSX, or PDF"""
"""Export sync logs to CSV, XLSX, or PDF"""
try
:
try
:
...
@@ -2524,6 +3082,11 @@ def client_report_detail(client_id):
...
@@ -2524,6 +3082,11 @@ def client_report_detail(client_id):
from
app.models
import
ReportSync
,
Bet
,
ExtractionStats
,
APIToken
,
ClientActivity
,
MatchReport
from
app.models
import
ReportSync
,
Bet
,
ExtractionStats
,
APIToken
,
ClientActivity
,
MatchReport
from
datetime
import
timedelta
from
datetime
import
timedelta
# Check for export request
export_format
=
request
.
args
.
get
(
'export'
)
if
export_format
:
return
export_client_report
(
client_id
,
export_format
)
# Get filter parameters
# Get filter parameters
date_range_filter
=
request
.
args
.
get
(
'date_range'
,
'today'
)
.
strip
()
date_range_filter
=
request
.
args
.
get
(
'date_range'
,
'today'
)
.
strip
()
start_date_filter
=
request
.
args
.
get
(
'start_date'
,
''
)
.
strip
()
start_date_filter
=
request
.
args
.
get
(
'start_date'
,
''
)
.
strip
()
...
@@ -2689,6 +3252,11 @@ def match_report_detail(client_id, match_id):
...
@@ -2689,6 +3252,11 @@ def match_report_detail(client_id, match_id):
from
app.models
import
MatchReport
,
Bet
,
BetDetail
,
APIToken
,
ClientActivity
from
app.models
import
MatchReport
,
Bet
,
BetDetail
,
APIToken
,
ClientActivity
from
datetime
import
timedelta
from
datetime
import
timedelta
# Check for export request
export_format
=
request
.
args
.
get
(
'export'
)
if
export_format
:
return
export_match_report
(
client_id
,
match_id
,
export_format
)
# Get filter parameters
# Get filter parameters
date_range_filter
=
request
.
args
.
get
(
'date_range'
,
'today'
)
.
strip
()
date_range_filter
=
request
.
args
.
get
(
'date_range'
,
'today'
)
.
strip
()
start_date_filter
=
request
.
args
.
get
(
'start_date'
,
''
)
.
strip
()
start_date_filter
=
request
.
args
.
get
(
'start_date'
,
''
)
.
strip
()
...
@@ -2822,6 +3390,11 @@ def bet_detail(client_id, match_id, bet_uuid):
...
@@ -2822,6 +3390,11 @@ def bet_detail(client_id, match_id, bet_uuid):
try
:
try
:
from
app.models
import
Bet
,
BetDetail
,
APIToken
,
ClientActivity
from
app.models
import
Bet
,
BetDetail
,
APIToken
,
ClientActivity
# Check for export request
export_format
=
request
.
args
.
get
(
'export'
)
if
export_format
:
return
export_bet_detail
(
client_id
,
match_id
,
bet_uuid
,
export_format
)
# Get filter parameters
# Get filter parameters
date_range_filter
=
request
.
args
.
get
(
'date_range'
,
'today'
)
.
strip
()
date_range_filter
=
request
.
args
.
get
(
'date_range'
,
'today'
)
.
strip
()
start_date_filter
=
request
.
args
.
get
(
'start_date'
,
''
)
.
strip
()
start_date_filter
=
request
.
args
.
get
(
'start_date'
,
''
)
.
strip
()
...
...
app/templates/main/bet_detail.html
View file @
58f3762b
...
@@ -129,9 +129,21 @@
...
@@ -129,9 +129,21 @@
</ol>
</ol>
</nav>
</nav>
</div>
</div>
<a
href=
"{{ url_for('main.match_report_detail', client_id=client_id, match_id=match_id, **filters) }}"
class=
"btn btn-secondary"
>
<div
class=
"d-flex gap-2"
>
<i
class=
"fas fa-arrow-left"
></i>
Back to Match
<div
class=
"btn-group"
>
</a>
<button
type=
"button"
class=
"btn btn-primary dropdown-toggle"
data-bs-toggle=
"dropdown"
>
<i
class=
"fas fa-download"
></i>
Export
</button>
<ul
class=
"dropdown-menu"
>
<li><a
class=
"dropdown-item"
href=
"{{ url_for('main.bet_detail', client_id=client_id, match_id=match_id, bet_uuid=bet_uuid, export='csv', **filters) }}"
>
Export as CSV
</a></li>
<li><a
class=
"dropdown-item"
href=
"{{ url_for('main.bet_detail', client_id=client_id, match_id=match_id, bet_uuid=bet_uuid, export='xlsx', **filters) }}"
>
Export as Excel
</a></li>
<li><a
class=
"dropdown-item"
href=
"{{ url_for('main.bet_detail', client_id=client_id, match_id=match_id, bet_uuid=bet_uuid, export='pdf', **filters) }}"
>
Export as PDF
</a></li>
</ul>
</div>
<a
href=
"{{ url_for('main.match_report_detail', client_id=client_id, match_id=match_id, **filters) }}"
class=
"btn btn-secondary"
>
<i
class=
"fas fa-arrow-left"
></i>
Back to Match
</a>
</div>
</div>
</div>
<!-- Bet Summary Cards -->
<!-- Bet Summary Cards -->
...
...
app/templates/main/client_report_detail.html
View file @
58f3762b
...
@@ -186,9 +186,21 @@
...
@@ -186,9 +186,21 @@
</ol>
</ol>
</nav>
</nav>
</div>
</div>
<a
href=
"{{ url_for('main.reports', **filters) }}"
class=
"btn btn-secondary"
>
<div
class=
"d-flex gap-2"
>
<i
class=
"fas fa-arrow-left"
></i>
Back to Reports
<div
class=
"btn-group"
>
</a>
<button
type=
"button"
class=
"btn btn-primary dropdown-toggle"
data-bs-toggle=
"dropdown"
>
<i
class=
"fas fa-download"
></i>
Export
</button>
<ul
class=
"dropdown-menu"
>
<li><a
class=
"dropdown-item"
href=
"{{ url_for('main.client_report_detail', client_id=client_id, export='csv', **filters) }}"
>
Export as CSV
</a></li>
<li><a
class=
"dropdown-item"
href=
"{{ url_for('main.client_report_detail', client_id=client_id, export='xlsx', **filters) }}"
>
Export as Excel
</a></li>
<li><a
class=
"dropdown-item"
href=
"{{ url_for('main.client_report_detail', client_id=client_id, export='pdf', **filters) }}"
>
Export as PDF
</a></li>
</ul>
</div>
<a
href=
"{{ url_for('main.reports', **filters) }}"
class=
"btn btn-secondary"
>
<i
class=
"fas fa-arrow-left"
></i>
Back to Reports
</a>
</div>
</div>
</div>
<!-- Summary Cards - First Row (3 cards) -->
<!-- Summary Cards - First Row (3 cards) -->
...
...
app/templates/main/match_report_detail.html
View file @
58f3762b
...
@@ -187,9 +187,21 @@
...
@@ -187,9 +187,21 @@
</ol>
</ol>
</nav>
</nav>
</div>
</div>
<a
href=
"{{ url_for('main.client_report_detail', client_id=client_id, **filters) }}"
class=
"btn btn-secondary"
>
<div
class=
"d-flex gap-2"
>
<i
class=
"fas fa-arrow-left"
></i>
Back to Client
<div
class=
"btn-group"
>
</a>
<button
type=
"button"
class=
"btn btn-primary dropdown-toggle"
data-bs-toggle=
"dropdown"
>
<i
class=
"fas fa-download"
></i>
Export
</button>
<ul
class=
"dropdown-menu"
>
<li><a
class=
"dropdown-item"
href=
"{{ url_for('main.match_report_detail', client_id=client_id, match_id=match_id, export='csv', **filters) }}"
>
Export as CSV
</a></li>
<li><a
class=
"dropdown-item"
href=
"{{ url_for('main.match_report_detail', client_id=client_id, match_id=match_id, export='xlsx', **filters) }}"
>
Export as Excel
</a></li>
<li><a
class=
"dropdown-item"
href=
"{{ url_for('main.match_report_detail', client_id=client_id, match_id=match_id, export='pdf', **filters) }}"
>
Export as PDF
</a></li>
</ul>
</div>
<a
href=
"{{ url_for('main.client_report_detail', client_id=client_id, **filters) }}"
class=
"btn btn-secondary"
>
<i
class=
"fas fa-arrow-left"
></i>
Back to Client
</a>
</div>
</div>
</div>
<!-- Match Summary Cards -->
<!-- Match Summary Cards -->
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment