Viết hàm Python để tự động xuất dữ liệu Sổ Cái ra file Excel

Ở Bài học dưới đây chúng ta viết hàm Python sử dụng thư viện openpyxl kết hợp với pandas để chuyển đổi dữ liệu từ hàm get_general_ledger() (ở Bài 2.1) thành file Excel (.xlsx) được trình bày chuẩn mực theo mẫu sổ sách kế toán.

1. Cài đặt thư viện bổ trợ

Nếu chưa cài đặt openpyxl, bạn chạy lệnh sau trong terminal:

Bash
pip install openpyxl

2. Mã nguồn Python: Hàm export_ledger_to_excel

import openpyxl
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
from openpyxl.utils import get_column_letter
from get_ledger_script import get_general_ledger  # Hàm lấy dữ liệu Sổ Cái từ Bài 2.1


def export_ledger_to_excel(
    account_code: str,
    from_date: str,
    to_date: str,
    output_file: str = None,
    db_name: str = "accounting_mini.db"
) -> str:
    """
    Xuất dữ liệu Sổ Cái (General Ledger) ra file Excel (.xlsx) 
    với đầy đủ tiêu đề, kẻ khung, định dạng số tiền và tự động căn chỉnh độ rộng cột.
    """
    # 1. Trích xuất dữ liệu Sổ Cái từ Database
    ledger_data = get_general_ledger(account_code, from_date, to_date, db_name)
    df = ledger_data["transactions_df"]

    if output_file is None:
        output_file = f"So_Cai_TK_{account_code}_{from_date}_den_{to_date}.xlsx"

    # 2. Khởi tạo Workbook và Worksheet
    wb = openpyxl.Workbook()
    ws = wb.active
    ws.title = f"So_Cai_{account_code}"
    ws.views.sheetView[0].showGridLines = True  # Hiển thị đường lưới mặc định

    # --- ĐỊNH NGHĨA STYLES (Định dạng) ---
    font_company = Font(name="Times New Roman", size=10, bold=True)
    font_title = Font(name="Times New Roman", size=16, bold=True, color="002060")
    font_subtitle = Font(name="Times New Roman", size=11, italic=True)
    font_header = Font(name="Times New Roman", size=11, bold=True, color="FFFFFF")
    font_bold = Font(name="Times New Roman", size=11, bold=True)
    font_regular = Font(name="Times New Roman", size=11)

    # Mầu nền Header (Xanh đậm)
    fill_header = PatternFill(start_color="1F4E78", end_color="1F4E78", fill_type="solid")
    # Màu nền cho dòng Tổng cộng (Xanh nhạt)
    fill_total = PatternFill(start_color="D9E1F2", end_color="D9E1F2", fill_type="solid")

    # Đường kẻ khung (Border)
    thin_border_side = Side(border_style="thin", color="D9D9D9")
    dark_border_side = Side(border_style="thin", color="000000")
    double_bottom_side = Side(border_style="double", color="000000")

    border_cell = Border(left=thin_border_side, right=thin_border_side, top=thin_border_side, bottom=thin_border_side)
    border_total = Border(top=dark_border_side, bottom=double_bottom_side, left=thin_border_side, right=thin_border_side)

    # Căn lề
    align_center = Alignment(horizontal="center", vertical="center", wrap_text=True)
    align_left = Alignment(horizontal="left", vertical="center")
    align_right = Alignment(horizontal="right", vertical="center")

    # Format số tiền VNĐ
    currency_format = "#,##0"

    # 3. GHI THÔNG TIN TIÊU ĐỀ BÁO CÁO
    ws["A1"] = "ĐƠN VỊ: CÔNG TY TNHH ABC"
    ws["A1"].font = font_company
    ws["A2"] = "Mẫu số S03b-DN (Ban hành theo TT 200/2014/TT-BTC)"
    ws["A2"].font = Font(name="Times New Roman", size=9, italic=True)

    ws["A4"] = "SỔ CÁI TÀI KHỎAN"
    ws["A4"].font = font_title
    ws["A4"].alignment = align_center
    ws.merge_cells("A4:E4")

    ws["A5"] = f"Tài khoản: {ledger_data['account_code']} - {ledger_data['account_name']}"
    ws["A5"].font = font_bold
    ws["A5"].alignment = align_center
    ws.merge_cells("A5:E5")

    ws["A6"] = f"Từ ngày: {from_date}  -  Đến ngày: {to_date}"
    ws["A6"].font = font_subtitle
    ws["A6"].alignment = align_center
    ws.merge_cells("A6:E6")

    # 4. GHI HEADER BẢNG DỮ LIỆU (Dòng 8)
    headers = ["Ngày GS", "Số CT", "Diễn giải", "Phát sinh Nợ", "Phát sinh Có"]
    for col_idx, header in enumerate(headers, 1):
        cell = ws.cell(row=8, column=col_idx, value=header)
        cell.font = font_header
        cell.fill = fill_header
        cell.alignment = align_center
        cell.border = Border(top=dark_border_side, bottom=dark_border_side)

    # 5. GHI DÒNG SỐ DƯ ĐẦU KỲ (Dòng 9)
    ws.cell(row=9, column=1, value="")
    ws.cell(row=9, column=2, value="")
    ws.cell(row=9, column=3, value="Số dư đầu kỳ").font = font_bold
    
    # Ghi nhận số dư đầu kỳ vào bên Nợ hoặc Có tùy theo thuộc tính tài khoản
    if ledger_data["normal_balance"] == "DEBIT":
        cell_debit = ws.cell(row=9, column=4, value=ledger_data["opening_balance"])
        cell_credit = ws.cell(row=9, column=5, value=0)
    else:
        cell_debit = ws.cell(row=9, column=4, value=0)
        cell_credit = ws.cell(row=9, column=5, value=ledger_data["opening_balance"])

    for col_idx in range(1, 6):
        c = ws.cell(row=9, column=col_idx)
        c.font = font_bold
        c.border = border_cell
        if col_idx in [4, 5]:
            c.number_format = currency_format
            c.alignment = align_right

    # 6. GHI CHI TIẾT CÁC PHÁT SINH TRONG KỲ
    current_row = 10
    if not df.empty:
        for _, row in df.iterrows():
            ws.cell(row=current_row, column=1, value=str(row["Ngày GS"])).alignment = align_center
            ws.cell(row=current_row, column=2, value=str(row["Số CT"])).alignment = align_center
            ws.cell(row=current_row, column=3, value=str(row["Diễn giải"])).alignment = align_left
            
            c_debit = ws.cell(row=current_row, column=4, value=float(row["Phát sinh Nợ"]))
            c_credit = ws.cell(row=current_row, column=5, value=float(row["Phát sinh Có"]))
            
            c_debit.alignment = align_right
            c_debit.number_format = currency_format
            c_credit.alignment = align_right
            c_credit.number_format = currency_format

            for col_idx in range(1, 6):
                cell = ws.cell(row=current_row, column=col_idx)
                cell.font = font_regular
                cell.border = border_cell

            current_row += 1

    # 7. GHI TỔNG PHÁT SINH & SỐ DƯ CUỐI KỲ (Bằng công thức Excel SUM)
    row_start_data = 10
    row_end_data = current_row - 1

    # Dòng Tổng phát sinh
    ws.cell(row=current_row, column=3, value="Cộng phát sinh trong kỳ").font = font_bold
    
    if not df.empty:
        cell_sum_debit = ws.cell(row=current_row, column=4, value=f"=SUM(D{row_start_data}:D{row_end_data})")
        cell_sum_credit = ws.cell(row=current_row, column=5, value=f"=SUM(E{row_start_data}:E{row_end_data})")
    else:
        cell_sum_debit = ws.cell(row=current_row, column=4, value=0)
        cell_sum_credit = ws.cell(row=current_row, column=5, value=0)

    for col_idx in range(1, 6):
        c = ws.cell(row=current_row, column=col_idx)
        c.font = font_bold
        c.fill = fill_total
        c.border = border_cell
        if col_idx in [4, 5]:
            c.number_format = currency_format
            c.alignment = align_right

    current_row += 1

    # Dòng Số dư cuối kỳ
    ws.cell(row=current_row, column=3, value="Số dư cuối kỳ").font = font_bold
    if ledger_data["normal_balance"] == "DEBIT":
        ws.cell(row=current_row, column=4, value=ledger_data["closing_balance"])
        ws.cell(row=current_row, column=5, value=0)
    else:
        ws.cell(row=current_row, column=4, value=0)
        ws.cell(row=current_row, column=5, value=ledger_data["closing_balance"])

    for col_idx in range(1, 6):
        c = ws.cell(row=current_row, column=col_idx)
        c.font = font_bold
        c.fill = fill_total
        c.border = border_total
        if col_idx in [4, 5]:
            c.number_format = currency_format
            c.alignment = align_right

    # 8. TỰ ĐỘNG CÂN CHỈNH ĐỘ RỘNG CỘT (AUTO-FIT COLUMN WIDTH)
    min_widths = [14, 14, 38, 20, 20]  # Độ rộng tối thiểu cho từng cột
    for col_idx, min_w in enumerate(min_widths, 1):
        col_letter = get_column_letter(col_idx)
        max_len = min_w
        for row in range(8, current_row + 1):
            val = ws.cell(row=row, column=col_idx).value
            if val and not str(val).startswith("="):
                max_len = max(max_len, len(str(val)) + 3)
        ws.column_dimensions[col_letter].width = max_len

    # 9. LƯU FILE EXCEL
    wb.save(output_file)
    print(f"[XUẤT BÁO CÁO THÀNH CÔNG] File Sổ Cái đã tạo tại: {output_file}")
    return output_file

 

3. Chạy Thử Nghiệm

Bạn có thể gọi hàm để xuất Sổ Cái cho Tài khoản 111 (Tiền mặt) trong tháng 09/2026:

if __name__ == "__main__":
    # Xuất Sổ Cái Tài khoản 111 ra file Excel
    export_ledger_to_excel(
        account_code="111",
        from_date="2026-09-01",
        to_date="2026-09-30"
    )

Đặc điểm định dạng của File Excel xuất ra:

  1. Tiêu đề chuẩn: Phông chữ Times New Roman, kích thước chữ và kiểu căn lề tương thích với mẫu sổ kế toán ban hành theo Thông tư 200/2014/TT-BTC.

  2. Kẻ khung chuyên nghiệp: Đường viền mảnh cho dữ liệu chi tiết, đường viền đôi ở chân trang số dư cuối kỳ, tô màu nền tiêu đề và dòng tổng cộng.

  3. Định dạng số tiền: Phân cách hàng nghìn bằng dấu phẩy (#,##0), căn lề phải cho cột tiền tệ và căn giữa cho ngày tháng/số chứng từ.

  4. Công thức động: Sử dụng trực tiếp công thức Excel =SUM(...) tại dòng Tổng phát sinh để khi mở file bằng Microsoft Excel hay Google Sheets, dữ liệu luôn duy trì tính chính xác.