Lập trình Trích xuất Báo cáo Kết quả Hoạt động Kinh doanh (P&L)

1. Mục tiêu bài học

Sau khi hoàn thành bài học này, người học có thể:

  • Nắm vững bản chất Báo cáo P&L (Profit & Loss / Income Statement): Hiểu rõ cơ chế tập hợp Doanh thu (TK loại 5, 7) và Chi phí (TK loại 6, 8) để tính toán các chỉ tiêu tài chính then chốt.

  • Xây dựng cấu trúc chỉ tiêu (Financial Line Items Framework): Thiết lập mô hình mapping mã tài khoản SQL vào các dòng báo cáo chuẩn (Doanh thu thuần, Giá vốn, Lợi nhuận gộp, Chi phí quản lý, Lợi nhuận thuần).

  • Lập trình SQL chiết xuất chỉ tiêu kinh doanh: Viết các câu lệnh truy vấn phân loại dòng tiền theo tính chất doanh thu / chi phí trong khoảng thời gian [from_date, to_date].

  • Đóng gói Python API Báo cáo P&L: Xây dựng hàm Python tính toán tự động Lợi nhuận gộp, Lợi nhuận trước thuế và Lợi nhuận sau thuế, xuất kết quả ra Pandas DataFrame chuẩn mực.

2. Kiến thức Nền tảng: Cấu trúc Báo cáo P&L

Khác với Bảng Cân đối Kế toán (phản ánh trạng thái tài sản tại một thời điểm), Báo cáo P&L phản ánh hiệu quả hoạt động trong một khoảng thời gian.

┌─────────────────────────────────────────────────────────────────────────┐
│              DOANH THU THUẦN (Doanh thu bán hàng - Giảm trừ)            │
└────────────────────────────────────┬────────────────────────────────────┘
                                     │  (-) Giá vốn hàng bán (TK 632)
                                     ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                      LỢI NHUẬN GỘP (Gross Profit)                       │
└────────────────────────────────────┬────────────────────────────────────┘
                                     │  (+) Doanh thu tài chính (TK 515)
                                     │  (-) Chi phí tài chính (TK 635)
                                     │  (-) Chi phí bán hàng & QLDN (TK 641, 642)
                                     ▼
┌─────────────────────────────────────────────────────────────────────────┐
│              LỢI NHUẬN THUẦN TỪ HOẠT ĐỘNG KINH DOANH                    │
└────────────────────────────────────┬────────────────────────────────────┘
                                     │  (+) Lợi nhuận khác (TK 711 - TK 811)
                                     ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                   LỢI NHUẬN TRƯỚC THUẾ (EBIT / EBT)                     │
└────────────────────────────────────┬────────────────────────────────────┘
                                     │  (-) Chi phí thuế TNDN (TK 821)
                                     ▼
┌─────────────────────────────────────────────────────────────────────────┐
│                    LỢI NHUẬN SAU THUẾ (Net Income)                      │
└─────────────────────────────────────────────────────────────────────────┘

Phương pháp tính toán từ Nhật ký chung:

  • Tài khoản Doanh thu (Loại 5, 7): Giá trị ghi nhận = $\sum \text{credit\_amount} - \sum \text{debit\_amount}$

  • Tài khoản Chi phí (Loại 6, 8): Giá trị ghi nhận = $\sum \text{debit\_amount} - \sum \text{credit\_amount}$

3. Lập trình Python & SQL Trích xuất Báo cáo P&L

Dưới đây là mã nguồn Python hoàn chỉnh truy vấn cơ sở dữ liệu và tổng hợp Báo cáo Kết quả Hoạt động Kinh doanh.

import sqlite3
import pandas as pd

def get_income_statement(from_date: str, to_date: str, db_name: str = "accounting_mini.db") -> dict:
    """
    Trích xuất Báo cáo Kết quả Hoạt động Kinh doanh (P&L) trong khoảng thời gian [from_date, to_date].
    """
    conn = sqlite3.connect(db_name)
    
    # 1. Truy vấn phát sinh của các Tài khoản Doanh thu & Chi phí (Loại 5, 6, 7, 8, 9)
    query = """
        SELECT 
            j.account_code,
            a.account_name,
            a.normal_balance,
            COALESCE(SUM(j.debit_amount), 0.0) AS total_debit,
            COALESCE(SUM(j.credit_amount), 0.0) AS total_credit
        FROM journal_entries j
        INNER JOIN transactions t ON j.transaction_id = t.transaction_id
        INNER JOIN accounts a ON j.account_code = a.account_code
        WHERE t.entry_date >= ? AND t.entry_date <= ?
          AND (j.account_code LIKE '5%' OR j.account_code LIKE '6%' 
               OR j.account_code LIKE '7%' OR j.account_code LIKE '8%')
        GROUP BY j.account_code, a.account_name, a.normal_balance
    """
    
    df_raw = pd.read_sql_query(query, conn, params=[from_date, to_date])
    conn.close()

    # Tạo dictionary tra cứu nhanh tổng số tiền ròng theo mã TK
    acc_summary = {}
    for _, row in df_raw.iterrows():
        code = str(row["account_code"])
        debit = row["total_debit"]
        credit = row["total_credit"]
        
        # TK Doanh thu (Loại 5, 7): Số tiền = Có - Nợ
        if code.startswith(('5', '7')):
            acc_summary[code] = credit - debit
        # TK Chi phí (Loại 6, 8): Số tiền = Nợ - Có
        else:
            acc_summary[code] = debit - credit

    def get_val(prefix: str) -> float:
        """Hàm trợ giúp cộng dồn số tiền của các tài khoản bắt đầu bằng prefix"""
        return sum(amount for code, amount in acc_summary.items() if code.startswith(prefix))

    # 2. TÍNH TOÁN CÁC CHỈ TIÊU P&L
    revenue_gross = get_val("511")                          # Doanh thu bán hàng
    revenue_deductions = get_val("521")                     # Các khoản giảm trừ doanh thu
    net_revenue = revenue_gross - revenue_deductions         # 1. Doanh thu thuần

    cogs = get_val("632")                                   # 2. Giá vốn hàng bán
    gross_profit = net_revenue - cogs                       # 3. Lợi nhuận gộp

    fin_revenue = get_val("515")                            # Doanh thu tài chính
    fin_expense = get_val("635")                            # Chi phí tài chính
    selling_expense = get_val("641")                        # Chi phí bán hàng
    admin_expense = get_val("642")                          # Chi phí quản lý doanh nghiệp

    # 4. Lợi nhuận thuần từ hoạt động kinh doanh
    operating_profit = gross_profit + fin_revenue - fin_expense - selling_expense - admin_expense

    other_income = get_val("711")                           # Thu nhập khác
    other_expense = get_val("811")                          # Chi phí khác
    other_profit = other_income - other_expense             # 5. Lợi nhuận khác

    profit_before_tax = operating_profit + other_profit     # 6. Lợi nhuận trước thuế (EBT)

    tax_expense = get_val("821")                            # Chi phí thuế TNDN
    net_profit = profit_before_tax - tax_expense            # 7. LỢI NHUẬN SAU THUẾ

    # 3. BẢNG TỔNG HỢP KẾT QUẢ
    pnl_structure = [
        {"Mã số": "01", "Chỉ tiêu": "1. Doanh thu bán hàng và cung cấp dịch vụ", "Số tiền": revenue_gross},
        {"Mã số": "02", "Chỉ tiêu": "2. Các khoản giảm trừ doanh thu", "Số tiền": revenue_deductions},
        {"Mã số": "10", "Chỉ tiêu": "3. DOANH THU THUẦN (10 = 01 - 02)", "Số tiền": net_revenue},
        {"Mã số": "11", "Chỉ tiêu": "4. Giá vốn hàng bán", "Số tiền": cogs},
        {"Mã số": "20", "Chỉ tiêu": "5. LỢI NHUẬN GỘP (20 = 10 - 11)", "Số tiền": gross_profit},
        {"Mã số": "21", "Chỉ tiêu": "6. Doanh thu hoạt động tài chính", "Số tiền": fin_revenue},
        {"Mã số": "22", "Chỉ tiêu": "7. Chi phí tài chính", "Số tiền": fin_expense},
        {"Mã số": "25", "Chỉ tiêu": "8. Chi phí bán hàng", "Số tiền": selling_expense},
        {"Mã số": "26", "Chỉ tiêu": "9. Chi phí quản lý doanh nghiệp", "Số tiền": admin_expense},
        {"Mã số": "30", "Chỉ tiêu": "10. LỢI NHUẬN THUẦN TỪ HĐKD [30 = 20 + (21-22) - (25+26)]", "Số tiền": operating_profit},
        {"Mã số": "31", "Chỉ tiêu": "11. Thu nhập khác", "Số tiền": other_income},
        {"Mã số": "32", "Chỉ tiêu": "12. Chi phí khác", "Số tiền": other_expense},
        {"Mã số": "40", "Chỉ tiêu": "13. Lợi nhuận khác (40 = 31 - 32)", "Số tiền": other_profit},
        {"Mã số": "50", "Chỉ tiêu": "14. TỔNG LỢI NHUẬN TRƯỚC THUẾ (50 = 30 + 40)", "Số tiền": profit_before_tax},
        {"Mã số": "51", "Chỉ tiêu": "15. Chi phí thuế TNDN hiện hành", "Số tiền": tax_expense},
        {"Mã số": "60", "Chỉ tiêu": "16. LỢI NHUẬN SAU THUẾ TNDN (60 = 50 - 51)", "Số tiền": net_profit},
    ]

    df_pnl = pd.DataFrame(pnl_structure)

    return {
        "df": df_pnl,
        "from_date": from_date,
        "to_date": to_date,
        "net_profit": net_profit
    }

4. Chạy Thử Nghiệm & In Báo Cáo P&L

Dưới đây là kịch bản nạp thêm một số giao dịch doanh thu/chi phí và chạy xuất báo cáo P&L.

if __name__ == "__main__":
    from add_entry_script import add_journal_entry  # Hàm từ Bài 1.2

    # Nạp bổ sung chi phí phát sinh trong tháng 09/2026
    add_journal_entry("2026-09-15", "PKX01", "Xuất kho hàng hóa bán (Giá vốn)", [
        {"account_code": "632", "debit": 8000000, "credit": 0},
        {"account_code": "156", "debit": 0, "credit": 8000000}
    ])

    add_journal_entry("2026-09-20", "PC003", "Chi tiền mặt cho chi phí quản lý", [
        {"account_code": "642", "debit": 2000000, "credit": 0},
        {"account_code": "111", "debit": 0, "credit": 2000000}
    ])

    # Thực thi xuất Báo cáo P&L
    pnl_result = get_income_statement("2026-09-01", "2026-09-30")
    df_pnl = pnl_result["df"]

    pd.set_option('display.max_columns', None)
    pd.set_option('display.width', 1000)
    pd.set_option('display.float_format', lambda x: '{:,.0f}'.format(x))

    print("==========================================================================")
    print("           BÁO CÁO KẾT QUẢ HOẠT ĐỘNG KINH DOANH (P&L)                     ")
    print(f"               Kỳ: {pnl_result['from_date']} đến {pnl_result['to_date']}               ")
    print("==========================================================================")
    print(df_pnl.to_string(index=False))
    print("==========================================================================")
    print(f"-> LỢI NHUẬN SAU THUẾ TRONG KỲ: {pnl_result['net_profit']:,.0f} VNĐ")
    print("==========================================================================")

Kết quả console xuất ra:

Plaintext
==========================================================================
           BÁO CÁO KẾT QUẢ HOẠT ĐỘNG KINH DOANH (P&L)                     
               Kỳ: 2026-09-01 đến 2026-09-30               
==========================================================================
Mã số               Chỉ tiêu                                       Số tiền
   01               1. Doanh thu bán hàng và cung cấp dịch vụ   15,000,000
   02               2. Các khoản giảm trừ doanh thu                      0
   10               3. DOANH THU THUẦN (10 = 01 - 02)           15,000,000
   11               4. Giá vốn hàng bán                          8,000,000
   20               5. LỢI NHUẬN GỘP (20 = 10 - 11)              7,000,000
   21               6. Doanh thu hoạt động tài chính                     0
   22               7. Chi phí tài chính                                 0
   25               8. Chi phí bán hàng                                  0
   26               9. Chi phí quản lý doanh nghiệp              2,000,000
   30               10. LỢI NHUẬN THUẦN TỪ HĐKD 
[30 = 20 + (21-22) - (25+26)] 5,000,000 31 11. Thu nhập khác 0 32 12. Chi phí khác 0 40 13. Lợi nhuận khác (40 = 31 - 32) 0 50 14. TỔNG LỢI NHUẬN TRƯỚC THUẾ (50 = 30 + 40) 5,000,000 51 15. Chi phí thuế TNDN hiện hành 0 60 16. LỢI NHUẬN SAU THUẾ TNDN (60 = 50 - 51) 5,000,000 ========================================================================== -> LỢI NHUẬN SAU THUẾ TRONG KỲ: 5,000,000 VNĐ ==========================================================================

5. Câu hỏi suy ngẫm & Bài tập thực hành

Bài tập 1: Trắc nghiệm Nguyên lý Kế toán

Tại sao Báo cáo P&L chỉ lấy phát sinh trong kỳ [from_date, to_date] mà không cộng số dư đầu kỳ như Bảng Cân đối Kế toán?

A. Vì các tài khoản Doanh thu và Chi phí (Loại 5 đến Loại 9) là tài khoản tạm thời, đã được kết chuyển về 0 vào cuối mỗi kỳ kế toán.

B. Vì cơ sở dữ liệu SQLite tự động xóa dữ liệu doanh thu cũ.

C. Vì doanh thu và chi phí chỉ phát sinh vào ngày cuối tháng.

Bài tập 2: Lập trình Mở rộng

Hãy viết thêm hàm tính tỷ suất Biên lợi nhuận gộp (Gross Margin %) và Biên lợi nhuận ròng (Net Margin %) từ kết quả trả về của hàm get_income_statement().

(Gợi ý: $\text{Gross Margin \%} = \frac{\text{Gross Profit}}{\text{Net Revenue}} \times 100$)

Đáp án gợi ý:

  • Bài tập 1:

    • Đáp án đúng là A. Tất cả tài khoản thuộc Báo cáo Kết quả Kinh doanh không có số dư chuyển sang kỳ sau; toàn bộ số dư cuối kỳ sẽ được kết chuyển sang TK 911 để xác định kết quả kinh doanh.

  • Bài tập 2:

    def calculate_margins(pnl_result: dict) -> dict:
        df = pnl_result["df"]
        net_rev = df.loc[df["Mã số"] == "10", "Số tiền"].values[0]
        gross_p = df.loc[df["Mã số"] == "20", "Số tiền"].values[0]
        net_p = df.loc[df["Mã số"] == "60", "Số tiền"].values[0]
    
        gross_margin = (gross_p / net_rev * 100) if net_rev > 0 else 0.0
        net_margin = (net_p / net_rev * 100) if net_rev > 0 else 0.0
    
        return {
            "gross_margin_pct": round(gross_margin, 2),
            "net_margin_pct": round(net_margin, 2)
        }
  •