Tới nội dung chính
hstation
Dịch vụDự ánVề tôiBlogThư viện nhạc

Tự động hoá · 9 phút đọc · 2026-09-11

Tự động hoá bán hàng với n8n: đọc file đơn hàng, cộng doanh thu và gửi email báo cáo

Một luồng n8n đọc CSV đơn hàng, lọc trạng thái Đã giao, cộng doanh thu rồi gửi báo cáo nội bộ qua email. Có cả bước kiểm tra đầu ra pass/fail để bắt lỗi dữ liệu và lỗi SMTP.

Luồng dưới đây chỉ đọc một file CSV, lọc các dòng có trạng thái Đã giao, cộng thành tiền và gửi một email báo cáo nội bộ. Bạn sẽ dựng được một bài kiểm thử có đầu ra pass/fail rõ ràng; luồng này chưa tự chốt đơn, chưa cập nhật trạng thái và chưa gửi email cho khách.

Tự động hoá bán hàng ở đây chỉ là báo cáo CSV, không phải tự động chốt đơn

Trong bài này, “tự động hoá bán hàng” có phạm vi hẹp: lấy dữ liệu đã có sẵn trong CSV, chọn đúng các đơn đã giao, cộng doanh thu rồi gửi kết quả qua email. Đây là một phần của sales automation, gần với tự động hoá báo cáo hơn là một hệ thống bán hàng tự động. Shop đang xuất đơn từ phần mềm khác, doanh nghiệp có người tổng hợp doanh thu cuối ngày, hoặc đội kỹ thuật chỉ cần một luồng kiểm tra nội bộ đều có thể áp dụng ngay. Bạn không cần bắt đầu bằng việc nối website, cổng thanh toán và CRM.

Luồng này không làm các việc như nhận đơn mới, tự động cập nhật trạng thái đơn hàng, trừ tồn kho, đối soát thanh toán hay tự động hóa quy trình chốt đơn. Nó cũng không chứng minh rằng dữ liệu trong CSV là đúng với hệ thống nguồn. Nếu file đã sai mã đơn hoặc sai thành tiền, n8n vẫn có thể cộng ra một kết quả rất “gọn” nhưng không có nghĩa kết quả đó đúng. Vì vậy, mục tiêu của bài là dựng một đường ống nhỏ có thể kiểm từng chặng: file có đọc được không, CSV có chuyển thành dòng dữ liệu không, có đúng năm dòng Đã giao không và email thử có xuất hiện trong Mailpit không.

Chuẩn bị file đơn hàng: CSV phải chứa giá trị, không chứa công thức

Mình dùng lại đúng tám đơn trong bài file Excel quản lý bán hàng trên blog này. Ở bài đó, bảng tính đã tính sẵn hai con số làm mốc: doanh thu các đơn "Đã giao" là 7.820.000, số đơn đã giao là 5. Luồng n8n ra đúng hai số này thì mới coi là chạy đúng.

Có một chi tiết phải xử lý trước. File CSV lưu từ bảng tính có công thức sẽ chứa chữ của công thức, kiểu =E2*F2, chứ không chứa kết quả — CSV chỉ là văn bản, nó không tính gì cả. Nên mình xuất lại một file chỉ có giá trị, cột Thành tiền là số đã nhân sẵn:

text
Ngày,Mã đơn,Khách hàng,Sản phẩm,Số lượng,Đơn giá,Thành tiền,Trạng thái
2026-09-01,DH001,Chị Lan,Áo thun cotton,12,145000,1740000,Đã giao
2026-09-01,DH002,Anh Dũng,Quần jean nam,3,320000,960000,Đã giao
2026-09-02,DH003,Chị Hoa,Váy hoa nhí,5,285000,1425000,Đang giao
2026-09-03,DH004,Anh Tuấn,Áo khoác gió,2,450000,900000,Đã giao
2026-09-04,DH005,Chị Mai,Áo thun cotton,20,145000,2900000,Đã huỷ
2026-09-05,DH006,Anh Nam,Quần short kaki,8,190000,1520000,Đã giao
2026-09-06,DH007,Chị Thu,Váy hoa nhí,4,285000,1140000,Đang giao
2026-09-08,DH008,Anh Bình,Áo khoác gió,6,450000,2700000,Đã giao

Tên cột giữ nguyên tiếng Việt có dấu. Node Code phía sau gọi đúng tên cột này, nên bạn đổi tên cột thì phải đổi cả trong mã.

Cái bẫy đầu tiên: n8n không cho đọc file ở thư mục tùy ý

Lượt chạy đầu, mình trỏ node đọc file tới bản CSV nằm trong thư mục dự án trên ổ H. Node đọc file chuyển đỏ ngay, cả lượt chạy kết thúc ở trạng thái error, và thông báo lỗi nguyên văn chỉ có một câu:

text
Access to the file is not allowed.

Câu đó không nói file được phép nằm ở đâu. Mình tải toàn bộ dữ liệu của lượt chạy lỗi về để tìm, và không có gợi ý đường dẫn nào trong đó.

Lượt chạy đầu trong n8n 2.35.7, khi node đọc file trỏ ra ngoài thư mục được phép. Node "Bấm chạy thử" viền xanh có dấu tích và đường nối ghi 1 item; node "Đọc file đơn hàng" viền đỏ với dấu gạch chéo trong vòng tròn đỏ; ba node phía sau là "Tách bảng CSV", "Cộng doanh thu đã giao", "Gửi email báo cáo" vẫn xám vì luồng dừng ngay tại đó.
Lượt chạy đầu trong n8n 2.35.7, khi node đọc file trỏ ra ngoài thư mục được phép. Node "Bấm chạy thử" viền xanh có dấu tích và đường nối ghi 1 item; node "Đọc file đơn hàng" viền đỏ với dấu gạch chéo trong vòng tròn đỏ; ba node phía sau là "Tách bảng CSV", "Cộng doanh thu đã giao", "Gửi email báo cáo" vẫn xám vì luồng dừng ngay tại đó.

Câu trả lời nằm trong cấu hình mặc định của chính n8n: biến N8N_RESTRICT_FILE_ACCESS_TO mặc định là ~/.n8n-files. Trên Windows, đó là thư mục .n8n-files nằm trong thư mục người dùng của bạn. Mình chép file vào đó, đổi đường dẫn trong node, và lượt chạy sau qua ngay. Muốn đọc ở thư mục khác thì đặt lại biến trên trước khi khởi động n8n — mình chưa thử cách này, nên bài không hướng dẫn nó.

Dựng năm node cho luồng tự động hoá bán hàng

Luồng gồm năm node nối thẳng hàng. Tên node dưới đây là tên mình đặt, bạn đặt tên khác cũng được; tên loại node là tên hiện trong n8n 2.35.7, bản mình dùng để chạy toàn bộ bài này.

Node 1 — Manual Trigger

Thêm node Manual Trigger để bấm chạy bằng tay. Khi đang dựng, chạy tay cho thấy kết quả của từng node ngay lập tức; chỉ nên thay bằng lịch chạy sau khi cả luồng đã ra đúng số.

Node 2 — Read/Write Files from Disk

Chọn thao tác đọc file (giao diện ghi là Read File(s) From Disk), rồi điền đường dẫn tới file trong thư mục .n8n-files. Mình dùng dấu gạch chéo xuôi:

text
C:/Users/<tên-người-dùng>/.n8n-files/don-hang.csv

Chạy riêng node này: đầu ra là 1 item chứa file ở dạng nhị phân, trong trường data. Thấy lỗi "Access to the file is not allowed." thì quay lại mục trên — file đang nằm sai thư mục.

Node 3 — Extract from File

Chọn chế độ CSV, trường nhị phân để mặc định là data. Chạy riêng node này: đầu ra phải là 8 item, mỗi item mang đủ tám cột Ngày, Mã đơn, Khách hàng, Sản phẩm, Số lượng, Đơn giá, Thành tiền, Trạng thái. Ra 1 item thì dòng tiêu đề hoặc dấu phân cách của file đang có vấn đề.

Node 4 — Code

Chọn chế độ Run Once for All Items, ngôn ngữ JavaScript, rồi dán nguyên đoạn này:

javascript
// Gom mọi dòng đơn hàng từ file CSV, chỉ cộng những đơn "Đã giao".
const dong = $input.all().map((i) => i.json)
const daGiao = dong.filter((d) => d['Trạng thái'] === 'Đã giao')
const doanhThu = daGiao.reduce((t, d) => t + Number(d['Thành tiền']), 0)
return [{
  json: {
    tongSoDon: dong.length,
    soDonDaGiao: daGiao.length,
    doanhThuDaGiao: doanhThu,
    doanhThuChu: doanhThu.toLocaleString('vi-VN') + ' đ',
  },
}]

Phép lọc dùng ===, tức so khớp tuyệt đối: "đã giao" viết thường, "Đã giao " thừa một dấu cách hay "Da giao" thiếu dấu đều bị coi là khác và không được cộng. Chặt như vậy để khỏi cộng nhầm, đổi lại dữ liệu nguồn phải gõ thống nhất.

Node Code "Cộng doanh thu đã giao" mở ra sau lượt chạy. Cột INPUT ghi "Tách bảng CSV 8 items" và liệt kê các cột của đơn đầu tiên: Ngày 2026-09-01, Mã đơn DH001, Khách hàng Chị Lan, Sản phẩm Áo thun cotton, Số lượng 12, Đơn giá 145000, Thành tiền 1740000, Trạng thái Đã giao. Ở giữa là chế độ Run Once for All Items, ngôn ngữ JavaScript và đoạn mã. Cột OUTPUT là 1 item với tongSoDon 8, soDonDaGiao 5, doanhThuDaGiao 7820000.
Node Code "Cộng doanh thu đã giao" mở ra sau lượt chạy. Cột INPUT ghi "Tách bảng CSV 8 items" và liệt kê các cột của đơn đầu tiên: Ngày 2026-09-01, Mã đơn DH001, Khách hàng Chị Lan, Sản phẩm Áo thun cotton, Số lượng 12, Đơn giá 145000, Thành tiền 1740000, Trạng thái Đã giao. Ở giữa là chế độ Run Once for All Items, ngôn ngữ JavaScript và đoạn mã. Cột OUTPUT là 1 item với tongSoDon 8, soDonDaGiao 5, doanhThuDaGiao 7820000.

Node 5 — Send Email

Điền máy chủ SMTP mà bạn dùng để thử; mình dùng Mailpit chạy trên máy, nghe ở 127.0.0.1 cổng 1025, không mật khẩu. Chọn định dạng thư là Text. Hai ô tiêu đề và nội dung phải ở chế độ biểu thức (Expression) thì mới lấy được số từ node Code. Tiêu đề:

text
Báo cáo bán hàng: {{ $json.soDonDaGiao }} đơn đã giao, doanh thu {{ $json.doanhThuChu }}

Nội dung:

text
Tổng số đơn trong file: {{ $json.tongSoDon }}
Đơn đã giao: {{ $json.soDonDaGiao }}
Doanh thu đã giao: {{ $json.doanhThuChu }}

Báo cáo do n8n tự tổng hợp lúc {{ $now.format("HH:mm dd/MM/yyyy") }}.

Hai lỗi mình từng dính ở luồng trước, áp luôn từ đầu để khỏi vấp lại. Thứ nhất, nội dung thư phải đặt đúng vào ô thân thư của node; ở một luồng trước mình đặt nhầm tham số và nhận về một lá thư rỗng, không có lỗi nào báo. Thứ hai, múi giờ mặc định của n8n là America/New_York: vào phần cài đặt của workflow đổi sang Asia/Ho_Chi_Minh, nếu không giờ ghi trong báo cáo lệch 11 tiếng so với giờ Việt Nam.

Chạy thử và đối chiếu: 8 đơn vào, 5 đơn đã giao, 7.820.000 đ

Bấm chạy cả luồng. Cả năm node xanh, và số item trên từng đường nối kể lại đúng câu chuyện: một file đi vào, tám dòng đơn được tách ra, một dòng báo cáo đi tới node gửi thư.

Lượt chạy thành công: cả năm node "Bấm chạy thử", "Đọc file đơn hàng", "Tách bảng CSV", "Cộng doanh thu đã giao", "Gửi email báo cáo" đều viền xanh có dấu tích. Các đường nối ghi lần lượt 1 item, 1 item, 8 items, 1 item, 1 item.
Lượt chạy thành công: cả năm node "Bấm chạy thử", "Đọc file đơn hàng", "Tách bảng CSV", "Cộng doanh thu đã giao", "Gửi email báo cáo" đều viền xanh có dấu tích. Các đường nối ghi lần lượt 1 item, 1 item, 8 items, 1 item, 1 item.

Đối chiếu bằng tay để chắc phép cộng đúng, không chỉ tin con số n8n in ra. Năm đơn đã giao là DH001, DH002, DH004, DH006, DH008:

text
1.740.000 + 960.000 + 900.000 + 1.520.000 + 2.700.000 = 7.820.000

Ba đơn không được cộng là DH003 và DH007 (Đang giao) cùng DH005 (Đã huỷ). Kết quả khớp đúng con số bảng tính đã tính ở bài Excel. Thư tới Mailpit ngay sau đó:

Thư báo cáo bắt được trong Mailpit, gửi từ bao-cao@wp-demo.test tới chu-shop@wp-demo.test, tiêu đề "Báo cáo bán hàng: 5 đơn đã giao, doanh thu 7.820.000 đ", dung lượng 946 byte. Thân thư ghi tổng số đơn trong file 8, đơn đã giao 5, doanh thu đã giao 7.820.000 đ, và thời điểm tổng hợp 11:38 11/09/2026, khớp với dòng Date của chính lá thư.
Thư báo cáo bắt được trong Mailpit, gửi từ bao-cao@wp-demo.test tới chu-shop@wp-demo.test, tiêu đề "Báo cáo bán hàng: 5 đơn đã giao, doanh thu 7.820.000 đ", dung lượng 946 byte. Thân thư ghi tổng số đơn trong file 8, đơn đã giao 5, doanh thu đã giao 7.820.000 đ, và thời điểm tổng hợp 11:38 11/09/2026, khớp với dòng Date của chính lá thư.

Những gì luồng này chưa làm được

Luồng trên là bước đầu của tự động hoá bán hàng, không phải toàn bộ. Nó chạy bằng tay, chưa chạy theo lịch hằng ngày. Nó đọc CSV, chưa đọc thẳng file .xlsx hay Google Sheets. Thư mới đi tới máy chủ bắt thư trên máy, chưa qua nhà cung cấp thư thật, nên chưa biết thư có vào hộp thư rác không. Mình cũng chưa thử với file vài nghìn dòng.

Muốn đi tiếp, bước an toàn nhất là thay Manual Trigger bằng Schedule Trigger — nhưng chỉ sau khi luồng chạy tay đã ra đúng số, và nhớ kiểm lại múi giờ, vì lịch chạy cũng theo múi giờ của workflow.