Ở 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.
Nếu chưa cài đặt openpyxl, bạn chạy lệnh sau trong terminal:
pip install openpyxl
export_ledger_to_excelimport 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
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"
)
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.
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.
Đị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ừ.
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.