Skip to main content

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.

Export Formats

Excel Export

Export your transactions to an Excel file (.xlsx) with complete transaction details. Endpoint: GET /api/reports/export/excel
1

Select Time Range

Choose from:
  • This Month
  • Last Month
  • This Year
2

Click Export

Navigate to Reports and click the Export to Excel button.
3

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:
ColumnDescriptionSource
NgàyTransaction dateTransaction.date
Danh mụcCategory nameCategory.name
Nội dungDescriptionTransaction.description
Số tiềnAmountTransaction.amount
LoạiType (Income/Expense/Transfer)Transaction.type
Wallet nameWallet.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.

Data Transformation

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

# 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'
)
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

1

Regular Exports

Export your data monthly for backup and offline analysis.
2

Choose Appropriate Format

  • Use Excel for data manipulation and custom analysis
  • Use PDF for printing and professional reports
3

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.

Build docs developers (and LLMs) love