P&L từ dữ liệu giao dịch
Bạn sẽ học được
- Dựng báo cáo lãi lỗ (P&L) từ dữ liệu giao dịch.
- Phân loại doanh thu, chi phí nhất quán bằng bảng ánh xạ tài khoản.
- Tính doanh thu thuần sau giảm giá và hoàn tiền.
- Đọc biên lợi nhuận gộp và biên hoạt động.
- Tránh những lỗi phân loại làm sai toàn bộ báo cáo.
Khái niệm cốt lõi
P&L là báo cáo lãi lỗ trong một kỳ:
Doanh thu thuần
− Giá vốn hàng bán (COGS)
= Lợi nhuận gộp
− Chi phí hoạt động (opex)
= Lợi nhuận hoạt độngMỗi dòng là một nhóm tài khoản. Điều quan trọng không phải công thức — công thức đơn giản — mà là phân loại nhất quán: cùng một loại giao dịch phải vào cùng một dòng, ở mọi kỳ, với mọi người làm báo cáo.
Doanh thu thuần
Doanh thu trên báo cáo không phải tổng tiền khách trả:
Doanh thu thuần = doanh thu gộp − giảm giá − hoàn tiềnVí dụ một tháng:
| Dòng | Số tiền (triệu đồng) |
|---|---|
| Doanh thu gộp | 2.000 |
| Giảm giá | 120 |
| Hoàn tiền | 80 |
| Doanh thu thuần | 1.800 |
Bỏ giảm giá và hoàn tiền ra khỏi báo cáo là cách phổ biến nhất để làm đẹp doanh thu. Cả hai đều là chi phí thật.
Lưu ý về thuế GTGT: doanh thu trên P&L thường không gồm VAT. Nếu bảng giao dịch lưu giá đã có VAT, phải tách ra trước khi tính.
Phân loại bằng bảng ánh xạ
Đừng viết CASE dài trong mọi báo cáo. Tạo bảng ánh xạ tài khoản:
CREATE TABLE account_map (
account_code text PRIMARY KEY,
pnl_line text NOT NULL, -- 'revenue' | 'cogs' | 'opex'
sort_order int NOT NULL
);
INSERT INTO account_map VALUES
('4000', 'revenue', 10),
('4100', 'revenue', 20),
('5000', 'cogs', 30),
('6000', 'opex', 40),
('6100', 'opex', 50);Khi có tài khoản mới, thêm một dòng vào bảng thay vì sửa mọi truy vấn. Đây là cách duy nhất để nhiều người dùng chung một định nghĩa.
Truy vấn P&L theo tháng
SELECT
date_trunc('month', t.posted_at)::date AS thang,
SUM(t.amount) FILTER (WHERE m.pnl_line = 'revenue') AS doanh_thu,
SUM(t.amount) FILTER (WHERE m.pnl_line = 'cogs') AS cogs,
SUM(t.amount) FILTER (WHERE m.pnl_line = 'revenue')
- ABS(SUM(t.amount) FILTER (WHERE m.pnl_line = 'cogs')) AS loi_nhuan_gop,
SUM(t.amount) FILTER (WHERE m.pnl_line = 'opex') AS opex
FROM transactions t
JOIN account_map m ON m.account_code = t.account_code
WHERE t.posted_at >= date_trunc('year', CURRENT_DATE)
GROUP BY 1
ORDER BY 1;Quy ước dấu rất quan trọng: chọn một quy ước và giữ nguyên. Thông thường doanh thu dương, chi phí âm; khi báo cáo thì đảo dấu chi phí để hiển thị số dương. Trộn hai quy ước làm tổng sai mà không báo lỗi.
Đọc biên lợi nhuận
Biên gộp = lợi nhuận gộp / doanh thu thuần
Biên hoạt động = lợi nhuận hoạt động / doanh thu thuầnVới doanh thu thuần 1.800 triệu, COGS 1.080 triệu, opex 450 triệu:
| Dòng | Số tiền | Biên |
|---|---|---|
| Doanh thu thuần | 1.800 | 100% |
| COGS | 1.080 | 60% |
| Lợi nhuận gộp | 720 | 40% |
| Opex | 450 | 25% |
| Lợi nhuận hoạt động | 270 | 15% |
Biên gộp 40% nói rằng mỗi 100 đồng doanh thu còn 40 đồng để trả chi phí cố định và lãi. Biên hoạt động 15% nói rằng sau tất cả chi phí vận hành còn 15 đồng.
So sánh biên giữa các kỳ chỉ có ý nghĩa khi cách phân loại không đổi. Nếu chuyển một khoản từ COGS sang opex, biên gộp tăng nhưng biên hoạt động không đổi — nhìn riêng biên gộp sẽ tưởng công ty tốt lên.
Những lỗi phân loại hay gặp
| Vấn đề | Hệ quả | Cách xử lý |
|---|---|---|
| Tính doanh thu gộp, bỏ giảm giá/hoàn | Doanh thu và lợi nhuận bị thổi phồng | Luôn trừ cả hai |
| Gồm VAT trong doanh thu | Doanh thu tăng ảo 8–10% | Tách VAT |
| Đưa lương bán hàng vào COGS | Biên gộp thay đổi tuỳ cách chia | Chốt quy ước, ghi tài liệu |
| Ghi nhận marketplace theo gross | Doanh thu và chi phí cùng phồng | Ghi net nếu chỉ là trung gian |
| Trộn kế toán dồn tích và tiền mặt | Doanh thu kỳ này lẫn kỳ khác | Chọn một cơ sở |
| Đổi cách phân loại giữa năm | Chuỗi thời gian gãy | Giữ nguyên hoặc trình bày lại toàn bộ |
Kiểm tra chéo trước khi công bố
Ba kiểm tra nhanh:
- Tổng doanh thu báo cáo = tổng hóa đơn − giảm giá − hoàn tiền trong cùng kỳ.
- Tổng chi phí báo cáo = tổng giao dịch chi phí theo bảng ánh xạ, không sót tài khoản.
- Lợi nhuận tính từ P&L khớp với lợi nhuận tính từ sổ cái — nếu lệch, có tài khoản chưa được ánh xạ.
Tài khoản chưa ánh xạ là lỗi nguy hiểm nhất vì nó âm thầm biến mất khỏi báo cáo: JOIN bỏ qua dòng không khớp. Dùng LEFT JOIN và đếm dòng NULL để phát hiện.
SELECT t.account_code, COUNT(*) AS so_giao_dich, SUM(t.amount) AS tong
FROM transactions t
LEFT JOIN account_map m ON m.account_code = t.account_code
WHERE m.account_code IS NULL
GROUP BY 1
ORDER BY 3;Thực hành
- Tạo bảng ánh xạ cho năm tài khoản của một công ty nhỏ.
- Viết truy vấn P&L theo tháng với doanh thu thuần.
- Tính biên gộp và biên hoạt động từ kết quả.
- Kiểm tra tài khoản chưa ánh xạ.
- Giải thích vì sao chuyển chi phí từ COGS sang opex làm biên gộp tăng.
Lỗi thường gặp
| Triệu chứng | Nguyên nhân | Cách sửa |
|---|---|---|
| Doanh thu cao hơn tiền thu | Không trừ giảm giá, hoàn tiền | Tính doanh thu thuần |
| Lợi nhuận không khớp sổ cái | Thiếu tài khoản trong ánh xạ | LEFT JOIN, xử lý NULL |
| Biên gộp nhảy bất thường | Phân loại đổi | Chốt quy ước |
| Doanh thu tăng ảo | Gồm VAT | Tách thuế |
| Số âm khó hiểu | Trộn quy ước dấu | Chốt quy ước dấu |
| Marketplace lãi lớn | Ghi gross thay vì net | Ghi net |
Tóm tắt
- P&L đi từ doanh thu thuần qua giá vốn đến lợi nhuận hoạt động.
- Doanh thu thuần phải trừ giảm giá và hoàn tiền.
- Phân loại bằng bảng ánh xạ, không bằng
CASErải rác. - Chốt quy ước dấu và giữ nguyên.
- Kiểm tra tài khoản chưa ánh xạ bằng
LEFT JOIN.
Dùng ← → để chuyển bài