Sau khi hoàn thành bài học này, người học có thể:
Hiểu bản chất và cấu trúc Bảng Cân đối Tài khoản (Bảng Cân đối Phát sinh): Nắm vững cơ chế tập hợp số liệu 6 cột (Số dư đầu kỳ, Phát sinh trong kỳ, Số dư cuối kỳ - chia theo Nợ/Có) cho toàn bộ Hệ thống Tài khoản.
Lập trình thuật toán SQL kiểm tra Cân bằng tổng thể: Viết câu truy vấn SQL gom nhóm (GROUP BY), liên kết dữ liệu và tính toán tự động số dư cho tất cả tài khoản phát sinh giao dịch.
Tự động hóa kiểm tra Ràng buộc Kế toán (Validation Engine): Xây dựng hàm Python xác minh quy tắc vàng của kế toán:
Đóng gói Báo cáo bằng Pandas DataFrame: Kết xuất dữ liệu ra bảng kiểm tra tổng hợp phục vụ lập Báo cáo Tài chính.
Bảng Cân đối Tài khoản là công cụ kiểm soát quan trọng nhất trước khi lập Báo cáo Tài chính (Bảng Cân đối Kế toán và Báo cáo Kết quả Kinh doanh).
┌────────────────────────────────────────────────────────────────────────────────────────┐
│ BẢNG CÂN ĐỐI TÀI KHỎAN │
├──────────────┬───────────────────┬───────────────────┬───────────────────┬─────────────┤
│ Mã / Tên TK │ SỐ DƯ ĐẦU KỲ │ PHÁT SINH TRONG KỲ│ SỐ DƯ CUỐI KỲ │ BẢN CHẤT │
│ │ Nợ │ Có │ Nợ │ Có │ Nợ │ Có │ TÀI KHỎAN │
├──────────────┼────────┼──────────┼────────┼──────────┼────────┼──────────┼─────────────┤
│ TK Loại 1-4 │ Balance Sheet │ Từ Journal │ Số dư chuyển kỳ │ Có số dư │
│ TK Loại 5-9 │ Thường bằng 0 │ Entries trong │ kế tiếp │ Tập hợp DT/│
│ │ │ khoảng thời gian│ │ CP (Kết chuyển)
└──────────────┴───────────────────┴───────────────────┴───────────────────┴─────────────┘
Số dư Đầu kỳ (trước from_date):
Tổng Nợ trước kỳ ($D_{pre}$) = $\sum \text{debit\_amount}$ với $t.entry\_date < \text{from\_date}$
Tổng Có trước kỳ ($C_{pre}$) = $\sum \text{credit\_amount}$ với $t.entry\_date < \text{from\_date}$
Nếu normal_balance = 'DEBIT':
$\text{Nợ Đầu kỳ} = \max(D_{pre} - C_{pre}, 0)$
$\text{Có Đầu kỳ} = \max(C_{pre} - D_{pre}, 0)$
Nếu normal_balance = 'CREDIT':
$\text{Nợ Đầu kỳ} = \max(D_{pre} - C_{pre}, 0)$
$\text{Có Đầu kỳ} = \max(C_{pre} - D_{pre}, 0)$
Phát sinh Trong kỳ (từ from_date đến to_date):
$\text{Nợ Phát sinh} = \sum \text{debit\_amount}$
$\text{Có Phát sinh} = \sum \text{credit\_amount}$
Số dư Cuối kỳ:
Tính theo giá trị ròng của $(\text{Đầu kỳ} + \text{Phát sinh})$ để đảm bảo không phản ánh hai bên cùng một lúc trên một tài khoản đơn lẻ.
Dưới đây là mã nguồn Python hoàn chỉnh tính toán Bảng Cân đối Tài khoản và kiểm tra các điều kiện cân bằng tự động.
import sqlite3
import pandas as pd
def get_trial_balance(from_date: str, to_date: str, db_name: str = "accounting_mini.db") -> dict:
"""
Tính toán Bảng Cân đối Tài khoản (Trial Balance) trong khoảng thời gian [from_date, to_date].
Trả về dict gồm: DataFrame chi tiết, Trạng thái Cân bằng (True/False), và Báo cáo chênh lệch.
"""
conn = sqlite3.connect(db_name)
# 1. Truy vấn danh mục tất cả tài khoản
query_accounts = "SELECT account_code, account_name, normal_balance FROM accounts ORDER BY account_code ASC"
accounts_df = pd.read_sql_query(query_accounts, conn)
# 2. Truy vấn Tổng phát sinh TRƯỚC KỲ (để tính Số dư Đầu kỳ)
query_pre = """
SELECT
j.account_code,
COALESCE(SUM(j.debit_amount), 0.0) AS pre_debit,
COALESCE(SUM(j.credit_amount), 0.0) AS pre_credit
FROM journal_entries j
INNER JOIN transactions t ON j.transaction_id = t.transaction_id
WHERE t.entry_date < ?
GROUP BY j.account_code
"""
pre_df = pd.read_sql_query(query_pre, conn, params=[from_date])
# 3. Truy vấn Tổng phát sinh TRONG KỲ
query_period = """
SELECT
j.account_code,
COALESCE(SUM(j.debit_amount), 0.0) AS period_debit,
COALESCE(SUM(j.credit_amount), 0.0) AS period_credit
FROM journal_entries j
INNER JOIN transactions t ON j.transaction_id = t.transaction_id
WHERE t.entry_date >= ? AND t.entry_date <= ?
GROUP BY j.account_code
"""
period_df = pd.read_sql_query(query_period, conn, params=[from_date, to_date])
conn.close()
# 4. Gộp dữ liệu (Merge DataFrames)
merged = pd.merge(accounts_df, pre_df, on="account_code", how="left").fillna(0.0)
merged = pd.merge(merged, period_df, on="account_code", how="left").fillna(0.0)
# 5. Thuật toán tính toán 6 cột Nợ/Có
trial_balance_rows = []
for _, row in merged.iterrows():
acc_code = row["account_code"]
acc_name = row["account_name"]
norm_bal = row["normal_balance"]
pre_d = row["pre_debit"]
pre_c = row["pre_credit"]
per_d = row["period_debit"]
per_c = row["period_credit"]
# Chỉ xử lý các tài khoản có phát sinh hoặc có dư
if pre_d == 0 and pre_c == 0 and per_d == 0 and per_c == 0:
continue
# Tính Số dư Đầu kỳ
net_pre = pre_d - pre_credit if norm_bal == "DEBIT" else pre_credit - pre_debit
open_debit = net_pre if (norm_bal == "DEBIT" and net_pre > 0) or (norm_bal == "CREDIT" and net_pre < 0) else 0.0
open_credit = net_pre if (norm_bal == "CREDIT" and net_pre > 0) or (norm_bal == "DEBIT" and net_pre < 0) else 0.0
if norm_bal == "CREDIT" and net_pre < 0:
open_debit = abs(net_pre)
open_credit = 0.0
elif norm_bal == "DEBIT" and net_pre < 0:
open_credit = abs(net_pre)
open_debit = 0.0
# Phát sinh trong kỳ
p_debit = per_d
p_credit = per_c
# Tính Số dư Cuối kỳ (Tính dựa trên dòng tiền ròng)
total_debit_side = open_debit + p_debit
total_credit_side = open_credit + p_credit
if total_debit_side >= total_credit_side:
close_debit = total_debit_side - total_credit_side
close_credit = 0.0
else:
close_debit = 0.0
close_credit = total_credit_side - total_debit_side
trial_balance_rows.append({
"Mã TK": acc_code,
"Tên Tài khoản": acc_name,
"Đầu kỳ Nợ": open_debit,
"Đầu kỳ Có": open_credit,
"Phát sinh Nợ": p_debit,
"Phát sinh Có": p_credit,
"Cuối kỳ Nợ": close_debit,
"Cuối kỳ Có": close_credit
})
df_tb = pd.DataFrame(trial_balance_rows)
# 6. Kiểm tra các điều kiện Cân bằng (Validation)
sum_open_debit = df_tb["Đầu kỳ Nợ"].sum() if not df_tb.empty else 0.0
sum_open_credit = df_tb["Đầu kỳ Có"].sum() if not df_tb.empty else 0.0
sum_p_debit = df_tb["Phát sinh Nợ"].sum() if not df_tb.empty else 0.0
sum_p_credit = df_tb["Phát sinh Có"].sum() if not df_tb.empty else 0.0
sum_close_debit = df_tb["Cuối kỳ Nợ"].sum() if not df_tb.empty else 0.0
sum_close_credit = df_tb["Cuối kỳ Có"].sum() if not df_tb.empty else 0.0
is_open_balanced = abs(sum_open_debit - sum_open_credit) < 1e-5
is_period_balanced = abs(sum_p_debit - sum_p_credit) < 1e-5
is_close_balanced = abs(sum_close_debit - sum_close_credit) < 1e-5
is_fully_balanced = is_open_balanced and is_period_balanced and is_close_balanced
summary = {
"is_balanced": is_fully_balanced,
"open_debit": sum_open_debit,
"open_credit": sum_open_credit,
"period_debit": sum_p_debit,
"period_credit": sum_p_credit,
"close_debit": sum_close_debit,
"close_credit": sum_close_credit,
"diff_open": sum_open_debit - sum_open_credit,
"diff_period": sum_p_debit - sum_p_credit,
"diff_close": sum_close_debit - sum_close_credit
}
return {
"df": df_tb,
"summary": summary
}
Dưới đây là kịch bản chạy thử nghiệm để kiểm tra tính cân bằng của Bảng Cân đối Tài khoản dựa trên các bút toán đã nhập.
if __name__ == "__main__":
pd.set_option('display.max_columns', None)
pd.set_option('display.width', 1000)
pd.set_option('display.float_format', lambda x: '{:,.0f}'.format(x))
# Thực thi tính toán Bảng Cân đối Tài khoản tháng 09/2026
tb_result = get_trial_balance("2026-09-01", "2026-09-30")
df_tb = tb_result["df"]
summary = tb_result["summary"]
print("=========================================================================================")
print(" BẢNG CÂN ĐỐI TÀI KHỎAN ")
print(" Kỳ: 01/09/2026 đến 30/09/2026 ")
print("=========================================================================================")
print(df_tb.to_string(index=False))
print("-----------------------------------------------------------------------------------------")
print(f"TỔNG CỘNG ĐẦU KỲ : Nợ = {summary['open_debit']:,.0f} | Có = {summary['open_credit']:,.0f} | Chênh lệch = {summary['diff_open']:,.0f}")
print(f"TỔNG CỘNG PHÁT SINH: Nợ = {summary['period_debit']:,.0f} | Có = {summary['period_credit']:,.0f} | Chênh lệch = {summary['diff_period']:,.0f}")
print(f"TỔNG CỘNG CUỐI KỲ : Nợ = {summary['close_debit']:,.0f} | Có = {summary['close_credit']:,.0f} | Chênh lệch = {summary['diff_close']:,.0f}")
print("-----------------------------------------------------------------------------------------")
if summary["is_balanced"]:
print("[XÁC NHẬN] BẢNG CÂN ĐỐI TÀI KHỎAN HOÀN TOÀN CÂN BẰNG! (Hệ thống dữ liệu hợp lệ)")
else:
print("[CẢNH BÁO LỖI] Bảng Cân đối bị MẤT CÂN BẰNG. Vui lòng kiểm tra lại các bút toán!")
print("=========================================================================================")
=========================================================================================
BẢNG CÂN ĐỐI TÀI KHỎAN
Kỳ: 01/09/2026 đến 30/09/2026
=========================================================================================
Mã TK Tên Tài khoản Đầu kỳ Nợ Đầu kỳ Có Phát sinh Nợ Phát sinh Có Cuối kỳ Nợ Cuối kỳ Có
111 Tiền mặt 0 0 115,000,000 40,000,000 75,000,000 0
112 Tiền gửi Ngân hàng 0 0 40,000,000 0 40,000,000 0
411 Vốn đầu tư của CSH 0 0 0 100,000,000 0 100,000,000
511 Doanh thu bán hàng 0 0 0 15,000,000 0 15,000,000
-----------------------------------------------------------------------------------------
TỔNG CỘNG ĐẦU KỲ : Nợ = 0 | Có = 0 | Chênh lệch = 0
TỔNG CỘNG PHÁT SINH: Nợ = 155,000,000 | Có = 155,000,000 | Chênh lệch = 0
TỔNG CỘNG CUỐI KỲ : Nợ = 115,000,000 | Có = 115,000,000 | Chênh lệch = 0
-----------------------------------------------------------------------------------------
[XÁC NHẬN] BẢNG CÂN ĐỐI TÀI KHỎAN HOÀN TOÀN CÂN BẰNG! (Hệ thống dữ liệu hợp lệ)
=========================================================================================
Nếu một Bảng Cân đối Tài khoản có TỔNG CỘNG PHÁT SINH NỢ lệch với TỔNG CỘNG PHÁT SINH CÓ, nguyên nhân kỹ thuật khả dĩ nhất trong cơ sở dữ liệu là gì?
A. Do người dùng nhập một tài khoản không có trong danh mục accounts.
B. Do hệ thống cho phép ghi dữ liệu mà không thực thi giao dịch nguyên tử (commit/rollback) khi bị mất cân bằng ở tầng ứng dụng hoặc thiếu ràng buộc database.
C. Do số dư đầu kỳ của tài khoản loại 1 bị tính sai.
Hãy viết thêm hàm export_trial_balance_to_excel(from_date, to_date, output_file) ứng dụng thư viện openpyxl để xuất Bảng Cân đối Tài khoản trên ra file Excel (.xlsx) với các tiêu đề cột được tô màu nhạt và đóng khung toàn bộ bảng.
Bài tập 1:
Đáp án đúng là B. Trong mô hình cơ sở dữ liệu kế toán, nếu cơ chế ghi bút toán (như hàm add_journal_entry ở Bài 1.2) hoạt động chuẩn xác với ràng buộc $\sum \text{Nợ} = \sum \text{Có}$ và giao dịch nguyên tử, tổng phát sinh Nợ và Có trong kỳ luôn luôn bằng nhau.
Bài tập 2:
Học viên có thể kết hợp thư viện openpyxl đã học ở bài trước, khởi tạo sheet tên Trial_Balance, ghi các dòng từ df_tb và tạo dòng Tổng cộng ở cuối sử dụng công thức Excel =SUM(C9:C12) cho các cột tương ứng.