Overview
FinAI allows you to export your transaction data in multiple formats for external analysis, archiving, or sharing with accountants. All exports respect your selected time range and wallet filters.
Excel Export
Export your transactions to an Excel file (.xlsx) with complete transaction details.
Endpoint: GET /api/reports/export/excel
Select Time Range
Choose from:
This Month
Last Month
This Year
Click Export
Navigate to Reports and click the Export to Excel button.
Download File
Your browser will automatically download a file named Bao_cao_{time_range}_{date}.xlsx.
PDF Export
Generate a formatted PDF report for printing or professional presentation.
Endpoint: GET /api/reports/export/pdf
PDF exports render a formatted HTML template optimized for printing, including summary statistics and transaction tables.
Excel Export Implementation
Data Structure
The Excel export includes the following columns:
Column Description Source Ngày Transaction date Transaction.dateDanh mục Category name Category.nameNội dung Description Transaction.descriptionSố tiền Amount Transaction.amountLoại Type (Income/Expense/Transfer) Transaction.typeVí Wallet name Wallet.name
Code Implementation
# From app/routes/report.py:131-163
@report_bp.route ( '/api/reports/export/excel' , methods = [ 'GET' ])
def export_excel ():
if api_login_required_check():
return jsonify({ 'status' : 'error' }), 401
user_id = session[ 'user_id' ]
time_range = request.args.get( 'time_range' , 'this_month' )
start_date, end_date = get_date_range(time_range)
# Query with proper joins to prevent N+1 queries
query = db.session.query(Transaction).filter(
Transaction.user_id == user_id,
Transaction.date >= start_date,
Transaction.date <= end_date
).outerjoin(
Category, Transaction.category_id == Category.id
).outerjoin(
Wallet, Transaction.wallet_id == Wallet.id
).order_by(Transaction.date.desc())
transactions = query.all()
The query uses outerjoin to handle transactions that may not have associated categories or wallets. This prevents missing data in exports.
Transactions are converted to a structured format:
# From app/routes/report.py:147-157
data_list = []
for t in transactions:
loai = ( "Chi tiêu" if t.type == 'chi'
else ( "Thu nhập" if t.type == 'thu'
else "Chuyển khoản" ))
data_list.append({
"Ngày" : t.date.strftime( ' %d /%m/%Y' ),
"Danh mục" : t.category.name if t.category else "Chưa phân loại" ,
"Nội dung" : t.description,
"Số tiền" : t.amount,
"Loại" : loai,
"Ví" : t.wallet.name if t.wallet else "Không xác định"
})
Type Mapping:
chi → Chi tiêu (Expense)
thu → Thu nhập (Income)
chuyen → Chuyển khoản (Transfer)
Excel File Generation
Pandas Implementation
Dependencies
# From app/routes/report.py:159-163
df = pd.DataFrame(data_list) if data_list else pd.DataFrame(
columns = [ "Ngày" , "Danh mục" , "Nội dung" ,
"Số tiền" , "Loại" , "Ví" ]
)
output = BytesIO()
with pd.ExcelWriter(output, engine = 'openpyxl' ) as writer:
df.to_excel(writer, index = False , sheet_name = 'Báo cáo' )
output.seek( 0 )
return send_file(
output,
download_name = f "Bao_cao_ { time_range } _ { date.today() } .xlsx" ,
as_attachment = True ,
mimetype = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
)
The export functionality requires:
pandas - DataFrame operations
openpyxl - Excel file writing
BytesIO - In-memory file handling
# From app/routes/report.py:5-6
import pandas as pd
from io import BytesIO
Files are generated in-memory using BytesIO to avoid writing temporary files to disk, improving performance and security.
PDF Export Implementation
Template Rendering
# From app/routes/report.py:165-192
@report_bp.route ( '/api/reports/export/pdf' , methods = [ 'GET' ])
def export_pdf ():
if api_login_required_check():
return redirect(url_for( 'auth.login' ))
user_id = session[ 'user_id' ]
time_range = request.args.get( 'time_range' , 'this_month' )
start_date, end_date = get_date_range(time_range)
# Query with proper table joins
transactions = db.session.query(Transaction).filter(
Transaction.user_id == user_id,
Transaction.date >= start_date,
Transaction.date <= end_date
).outerjoin(
Category, Transaction.category_id == Category.id
).outerjoin(
Wallet, Transaction.wallet_id == Wallet.id
).order_by(Transaction.date.desc()).all()
Summary Calculations
# From app/routes/report.py:181-182
total_income = sum (t.amount for t in transactions if t.type == 'thu' )
total_expense = sum (t.amount for t in transactions if t.type == 'chi' )
Template Variables
The PDF template receives:
# From app/routes/report.py:184-192
return render_template( 'user/pdf_report.html' ,
transactions = transactions, # List of Transaction objects
start_date = start_date.strftime( ' %d /%m/%Y' ),
end_date = end_date.strftime( ' %d /%m/%Y' ),
total_income = total_income, # Calculated sum
total_expense = total_expense, # Calculated sum
user_name = session.get( 'user_name' , 'Người dùng' ),
today = date.today().strftime( ' %d /%m/%Y' )
)
Time Range Filtering
get_date_range Function
Both export formats use the same date calculation logic:
# From app/routes/report.py:26-43
def get_date_range ( time_range ):
today = date.today()
if time_range == 'this_month' :
start_date = date(today.year, today.month, 1 )
last_day = calendar.monthrange(today.year, today.month)[ 1 ]
end_date = date(today.year, today.month, last_day)
elif time_range == 'last_month' :
first_of_this_month = date(today.year, today.month, 1 )
end_date = first_of_this_month - timedelta( days = 1 )
start_date = date(end_date.year, end_date.month, 1 )
elif time_range == 'year' :
start_date = date(today.year, 1 , 1 )
end_date = date(today.year, 12 , 31 )
else :
# Default to this_month
start_date = date(today.year, today.month, 1 )
last_day = calendar.monthrange(today.year, today.month)[ 1 ]
end_date = date(today.year, today.month, last_day)
return start_date, end_date
Time Range Options
this_month : First day to last day of current month
last_month : Full previous calendar month
year : January 1st to December 31st of current year
Transaction Model Reference
Exported data comes from the Transaction model:
# From app/models.py:78-103
class Transaction ( db . Model ):
__tablename__ = 'giaodich'
id = db.Column( 'MaGiaoDich' , db.String( 8 ), primary_key = True )
user_id = db.Column( 'MaNguoiDung' , db.String( 8 ),
db.ForeignKey( 'nguoidung.MaNguoiDung' ,
ondelete = 'CASCADE' ), nullable = False )
wallet_id = db.Column( 'MaNguonTien' , db.String( 8 ),
db.ForeignKey( 'nguontien.MaNguonTien' ),
nullable = False )
dest_wallet_id = db.Column( 'MaNguonTien_Dich' , db.String( 8 ),
db.ForeignKey( 'nguontien.MaNguonTien' ),
nullable = True )
category_id = db.Column( 'MaDanhMuc' , db.String( 8 ),
db.ForeignKey( 'danhmuc.MaDanhMuc' ,
ondelete = 'SET NULL' ), nullable = True )
type = db.Column( 'LoaiGiaoDich' , db.String( 20 ), nullable = False )
amount = db.Column( 'SoTien' , db.Float, nullable = False )
description = db.Column( 'MoTa' , db.String( 255 ))
date = db.Column( 'NgayGiaoDich' , db.Date, nullable = False )
created_at = db.Column( 'NgayTao' , db.DateTime, default = datetime.now)
# Relationships for easy access
wallet = db.relationship( 'Wallet' , foreign_keys = [wallet_id])
dest_wallet = db.relationship( 'Wallet' , foreign_keys = [dest_wallet_id])
category = db.relationship( 'Category' , foreign_keys = [category_id])
Best Practices
Regular Exports
Export your data monthly for backup and offline analysis.
Choose Appropriate Format
Use Excel for data manipulation and custom analysis
Use PDF for printing and professional reports
Verify Data
Check that exported transactions match your dashboard totals before archiving.
Exported files contain sensitive financial information. Store them securely and avoid sharing via unencrypted channels.
Troubleshooting
Empty Exports
If your export contains no data:
Verify you have transactions in the selected time range
Check that your wallet filter isn’t excluding all transactions
Ensure you’re logged in (exports require authentication)
Missing Categories
Transactions may show “Chưa phân loại” (Uncategorized) if:
The transaction was created without a category
The category was deleted after transaction creation (handled by ondelete='SET NULL')
Authentication Errors
# From app/routes/report.py:23-24
def api_login_required_check ():
return 'user_id' not in session
All export endpoints verify session authentication before processing requests.