DHDataHọc
TheoryLesson 6/86 min read+10 XP

Công nợ phải thu và DSO

Bạn sẽ học được

  • Chia tuổi nợ phải thu theo nhóm ngày quá hạn.
  • Tính DSO và đọc ý nghĩa của nó.
  • Phân biệt DSO thực tế với DSO tốt nhất có thể.
  • Tính tỷ lệ thu hồi và dự phòng nợ khó đòi.
  • Viết truy vấn tuổi nợ bằng SQL.

Khái niệm cốt lõi

Phải thu là tiền khách đã mua nhưng chưa trả. Hai câu hỏi cần trả lời:

  1. Bao nhiêu tiền đang bị giữ? — số dư phải thu.
  2. Bao lâu thì thu được? — tuổi nợ và DSO.

Tuổi nợ chia số dư thành các nhóm theo số ngày quá hạn so với ngày đến hạn trên hóa đơn. Nhóm càng cũ, khả năng thu càng thấp.

Ví dụ tuổi nợ

Số dư phải thu cuối quý: 900 triệu đồng.

Nhóm tuổiSố tiềnTỷ trọngTỷ lệ dự phòngDự phòng
0–30 ngày40044,4%1%4,0
31–60 ngày25027,8%3%7,5
61–90 ngày16017,8%10%16,0
Trên 90 ngày9010,0%40%36,0
Tổng900100%63,5

Hai điều đáng chú ý:

  • Chỉ 10% số dư nằm trên 90 ngày nhưng chiếm hơn nửa dự phòng.
  • Nếu nhóm trên 90 ngày tăng từ 90 lên 200 trong quý sau, đó là vấn đề nghiêm trọng hơn việc tổng phải thu tăng.

Nhìn tổng số dư không đủ. Cơ cấu tuổi nợ mới cho biết chất lượng.

DSO

code
DSO = số dư phải thu / doanh thu trong kỳ × số ngày của kỳ

Với doanh thu thuần quý 2.760 triệu (90 ngày) và phải thu cuối kỳ 900 triệu:

code
DSO = 900 / 2.760 × 90 = 29,3 ngày

Nghĩa là trung bình mất khoảng 29 ngày để thu tiền sau khi ghi doanh thu.

Điều kiện thanh toán là 30 ngày, DSO 29,3 ngày là tốt. Nếu điều kiện 30 ngày mà DSO 55 ngày, khách đang trả chậm hơn thỏa thuận gần một tháng.

DSO quá thấp cũng đáng kiểm tra: có thể doanh thu bị ghi thiếu, hoặc phần lớn khách trả trước và đang nằm ở doanh thu chờ.

DSO tốt nhất có thể

DSO thực tế bị kéo lên bởi các hóa đơn quá hạn. DSO tốt nhất giả sử toàn bộ số dư chưa quá hạn:

code
DSO tốt nhất = số dư chưa quá hạn / doanh thu × số ngày

Với số dư chưa quá hạn 650 triệu (400 + 250):

code
DSO tốt nhất = 650 / 2.760 × 90 = 21,2 ngày

Chênh lệch 29,3 − 21,2 = 8,1 ngày là phần do khách trả chậm. Con số này đo hiệu quả thu hồi, tách khỏi chính sách bán chịu.

Tỷ lệ thu hồi

code
Tỷ lệ thu hồi = tiền thu được / số phải thu đến hạn

Nếu trong quý phải thu đến hạn 1.000 triệu và thu được 940 triệu, tỷ lệ là 94%. Sáu phần trăm còn lại chuyển sang nhóm tuổi cao hơn ở quý sau.

Theo dõi tỷ lệ này theo tháng cho biết quy trình thu tiền đang tốt lên hay xấu đi, nhanh hơn DSO.

Truy vấn tuổi nợ

code
WITH da_thu AS (
  SELECT invoice_id, SUM(amount) AS tong_thu
  FROM payments
  GROUP BY 1
)
SELECT
  CASE
    WHEN CURRENT_DATE <= i.due_date THEN 'chua_den_han'
    WHEN CURRENT_DATE - i.due_date <= 30 THEN '0-30'
    WHEN CURRENT_DATE - i.due_date <= 60 THEN '31-60'
    WHEN CURRENT_DATE - i.due_date <= 90 THEN '61-90'
    ELSE '90+'
  END AS nhom_tuoi,
  COUNT(*) AS so_hoa_don,
  SUM(i.total - COALESCE(p.tong_thu, 0)) AS con_no
FROM invoices i
LEFT JOIN da_thu p ON p.invoice_id = i.id
WHERE i.status <> 'cancelled'
  AND i.total - COALESCE(p.tong_thu, 0) > 0
GROUP BY 1
ORDER BY MIN(GREATEST(CURRENT_DATE - i.due_date, 0));

Điểm cần chú ý:

  • Dùng LEFT JOIN để hóa đơn chưa có thanh toán nào vẫn được tính.
  • Tách nhóm "chưa đến hạn" riêng, đừng gộp vào 0–30 ngày — gộp sẽ làm nhóm 0–30 trông xấu hơn thực tế.
  • Điều kiện con_no > 0 loại hóa đơn đã thu đủ và hóa đơn thu thừa.

Truy vấn DSO

code
WITH phai_thu AS (
  SELECT SUM(i.total - COALESCE(p.tong_thu, 0)) AS so_du
  FROM invoices i
  LEFT JOIN (
    SELECT invoice_id, SUM(amount) AS tong_thu
    FROM payments GROUP BY 1
  ) p ON p.invoice_id = i.id
  WHERE i.status <> 'cancelled'
),
doanh_thu AS (
  SELECT SUM(total - discount - refund) AS doanh_thu_thuan
  FROM invoices
  WHERE issued_at >= CURRENT_DATE - INTERVAL '90 days'
    AND status <> 'cancelled'
)
SELECT
  ROUND(a.so_du / NULLIF(r.doanh_thu_thuan, 0) * 90, 1) AS dso_ngay
FROM phai_thu a
CROSS JOIN doanh_thu r;

NULLIF tránh chia cho 0 khi kỳ không có doanh thu. Không có nó, truy vấn lỗi hoặc trả NULL khó hiểu.

Dự phòng nợ khó đòi

Dự phòng là ước tính, không phải số thực. Cách làm phổ biến: gán tỷ lệ theo nhóm tuổi, nhân với số dư từng nhóm.

code
SELECT
  nhom_tuoi,
  con_no,
  ROUND(con_no * ty_le_du_phong, 0) AS du_phong
FROM (
  SELECT
    CASE
      WHEN CURRENT_DATE - due_date <= 30 THEN '0-30'
      WHEN CURRENT_DATE - due_date <= 60 THEN '31-60'
      WHEN CURRENT_DATE - due_date <= 90 THEN '61-90'
      ELSE '90+'
    END AS nhom_tuoi,
    SUM(con_no) AS con_no,
    CASE
      WHEN CURRENT_DATE - due_date <= 30 THEN 0.01
      WHEN CURRENT_DATE - due_date <= 60 THEN 0.03
      WHEN CURRENT_DATE - due_date <= 90 THEN 0.10
      ELSE 0.40
    END AS ty_le_du_phong
  FROM ar_aging_view
  GROUP BY 1, 4
) t;

Tỷ lệ phải dựa trên dữ liệu thu hồi thực tế của chính công ty, không sao chép từ nơi khác. Mỗi quý so sánh dự phòng với số thực sự mất để điều chỉnh.

Lỗi thường gặp

  • Nhìn tổng phải thu, bỏ cơ cấu tuổi: số dư không đổi nhưng chất lượng xấu đi.
  • Gộp chưa đến hạn vào 0–30: đánh giá sai.
  • Dùng INNER JOIN: bỏ hóa đơn chưa thu được đồng nào.
  • Tính DSO bằng doanh thu gộp: DSO bị thấp giả tạo.
  • Dùng DSO một tháng bất thường: nhiễu mùa vụ.
  • Không trừ hóa đơn huỷ: phải thu phồng.
  • Dự phòng theo cảm tính: không phản ánh rủi ro thật.

Thực hành

  1. Lập bảng tuổi nợ từ số dư 900 triệu với bốn nhóm.
  2. Tính DSO với doanh thu quý 2.760 triệu.
  3. Tính DSO tốt nhất với số dư chưa quá hạn 650 triệu.
  4. Tính dự phòng theo bảng tỷ lệ.
  5. Viết truy vấn tuổi nợ có tách nhóm chưa đến hạn.

Lỗi thường gặp

Triệu chứngNguyên nhânCách sửa
DSO tăng dầnKhách trả chậmXem nhóm tuổi cao
Phải thu không đổi, tiền mặt giảmCơ cấu tuổi xấu điTheo dõi tỷ lệ thu hồi
DSO thấp bất thườngBỏ sót doanh thuKiểm tra doanh thu chờ
Nhóm 0–30 quá lớnGộp chưa đến hạnTách nhóm riêng
Phải thu cao hơn sổ cáiHóa đơn huỷLọc trạng thái
Dự phòng luôn thiếuTỷ lệ không từ dữ liệu thậtHiệu chỉnh theo lịch sử

Tóm tắt

  • Tuổi nợ cho biết chất lượng phải thu, không chỉ số lượng.
  • DSO = phải thu / doanh thu × số ngày.
  • DSO tốt nhất tách phần chậm trả khỏi chính sách bán chịu.
  • Tỷ lệ thu hồi phản ứng nhanh hơn DSO.
  • Dự phòng dựa trên tỷ lệ mất thực tế theo nhóm tuổi.

Use ← → to move between lessons