Biến & biểu thức
Cách tham chiếu biến, định dạng giá trị, làm phép tính và viết trường biểu mẫu tính toán bằng ngôn ngữ mẫu.
Cách tham chiếu biến, định dạng giá trị, làm phép tính và viết trường biểu mẫu tính toán bằng ngôn ngữ mẫu
{{ … }}— dùng cho cả bước quy trình và biểu mẫu.
Đối tượng: người thiết kế quy trình và người xây dựng biểu mẫu. Không cần biết lập trình; các ví dụ có thể sao chép dùng ngay.
1. Mẫu là gì
Ở bất kỳ nơi nào bạn thấy chỗ giữ chỗ {{ … }}, bạn có thể lấy dữ liệu từ lần chạy và biến đổi nó. Văn bản ngoài dấu ngoặc được giữ nguyên; phần bên trong được tính toán và thay thế.
Chào {{ input.customer_name }}, tổng đơn hàng của bạn là {{ total }}.Mẫu có sẵn ở:
- Email — tiêu đề và nội dung
- Webhook — URL, header và body
- Tài liệu — nội dung PDF/DOCX được tạo
- Agent — câu lệnh gửi tới mô hình
- Set Variable — giá trị lưu vào biến
vars.* - Tích hợp (Integration) — ánh xạ trường đẩy sang nhà cung cấp
- Trường biểu mẫu tính toán — trường có giá trị được tính từ các trường khác
Có hai dạng, và sự khác biệt rất quan trọng (xem mục 7 bên dưới):
- Bề mặt chuỗi (nội dung email/webhook/tài liệu, câu lệnh agent) kết xuất toàn bộ mẫu thành văn bản, hỗ trợ vòng lặp và điều kiện.
- Bề mặt giá trị (Set Variable, trường tính toán, điều kiện rẽ nhánh) tính một biểu thức ra giá trị có kiểu — nên
{{ price * qty }}lưu số200, không phải chuỗi"200".
2. Tham chiếu biến
Mọi giá trị sinh ra trong lần chạy đều nằm dưới một phạm vi có tên:
| Phạm vi | Chứa gì | Ví dụ |
|---|---|---|
input.* | Dữ liệu khởi động quy trình (biểu mẫu bắt đầu) | {{ input.amount }} |
nodes.{id}.output.* | Kết quả của một bước | {{ nodes.agent_x3f9k2.output.summary }} |
vars.* | Biến bạn đặt bằng bước Set Variable | {{ vars.running_total }} |
trigger.* | Sự kiện kích hoạt lần chạy | {{ trigger.record.id }} |
parent.* | Dữ liệu lần chạy cha (bên trong thân ForEach) | {{ parent.input.company }} |
input.item.* | Phần tử hiện tại (bên trong thân ForEach) | {{ input.item.email }} |
input.index | Vị trí của phần tử, bắt đầu từ 0 (bên trong thân ForEach) | {{ input.index }} |
Trong thân ForEach, phần tử nằm dưới input.
Mỗi vòng lặp chạy như một lần chạy con, và bước ForEach đưa phần tử vào dữ liệu khởi động của lần chạy con đó. Vì vậy đường dẫn là input.item.…, không phải item.….
Viết {{ item.email }} hay {{ created_at }} sẽ không báo lỗi — đường dẫn đơn giản là không phân giải được, và bạn nhận về giá trị trống, không phải thông báo lỗi. Đây là lỗi thường gặp nhất khi dùng ForEach.
Nếu bạn đổi tên phần tử trong cấu hình ForEach (ví dụ thành dong_hang), đường dẫn đổi theo: input.dong_hang.….
Dùng bộ chọn biến (menu {{ / @ trong mọi ô mẫu) để chèn các biến này mà không cần nhớ đường dẫn — nó liệt kê mọi biến khả dụng cho bước hiện tại.
Truy cập trường — vào một đối tượng bằng dấu chấm:
{{ nodes.webhook_1.output.response.customer.id }}Truy cập theo chỉ số — vào danh sách bằng [n] (bắt đầu từ 0):
{{ input.line_items[0].name }}Khóa trường phải có dạng định danh (chữ cái, chữ số, gạch dưới; không bắt đầu bằng chữ số). Trình thiết kế biểu mẫu bắt buộc điều này, nên truy cập bằng dấu chấm luôn hoạt động.
3. Bộ lọc (Filters)
Bộ lọc biến đổi một giá trị. Viết value | filter — và nối nhiều cái tùy ý.
Bộ lọc tùy chỉnh
| Bộ lọc | Tác dụng | Ví dụ | Kết quả |
|---|---|---|---|
date("pattern") | Định dạng ngày | {{ input.created | date("dd-mm-yyyy") }} | 15-01-2024 |
datetime("pattern") | Định dạng ngày + giờ | {{ x | datetime("dd-mm-yyyy HH:MM") }} | 15-01-2024 10:30 |
number | Phân cách hàng nghìn | {{ amount | number }} | 1,234,567 |
number(2) | …với N chữ số thập phân | {{ amount | number(2) }} | 1,234.50 |
Ký hiệu mẫu ngày (phân biệt hoa thường — mm là tháng, MM là phút):
| Ký hiệu | Nghĩa | Ký hiệu | Nghĩa |
|---|---|---|---|
yyyy | năm 4 chữ số | HH | giờ (24h) |
yy | năm 2 chữ số | MM | phút |
mm | tháng | ss | giây |
dd | ngày |
Bộ lọc có sẵn thông dụng
| Bộ lọc | Ví dụ | Kết quả |
|---|---|---|
round(n) | {{ 3.14159 | round(2) }} | 3.14 |
upper / lower | {{ "hello" | upper }} | HELLO |
default(x) | {{ missing | default("N/A") }} | N/A |
join(", ") | {{ tags | join(", ") }} | a, b, c |
first / last | {{ items | first }} | phần tử đầu |
length | {{ items | length }} | 3 |
Phép biến đổi label (danh sách chọn)
Một trường danh sách chọn lưu giá trị của nó (ví dụ goods). Để hiển thị nhãn (ví dụ Hàng hóa) trong văn bản cho người đọc, thêm | label:
Loại: {{ input.category | label }}Bộ chọn biến cung cấp lựa chọn "nhãn hiển thị" cho mọi danh sách chọn nên bạn không phải gõ tay.
3b. Hàm ngày giờ
Đọc đồng hồ
| Hàm | Trả về | Ví dụ kết quả |
|---|---|---|
now() | Thời điểm hiện tại (ngày + giờ) | 2026-08-03T14:20:05+07:00 |
today() | Ngày hôm nay | 2026-08-03 |
year() | Năm hiện tại, dạng số | 2026 |
Ba hàm này đọc theo múi giờ của tổ chức bạn, không phải múi giờ của máy chủ. Các bộ lọc date / datetime và hàm date_diff cũng vậy: một dấu thời gian lưu trong hệ thống luôn ở dạng UTC, và chúng hiển thị nó theo giờ địa phương của tổ chức. Múi giờ lấy từ Lịch làm việc của tổ chức — cùng một thiết lập đã dùng cho thời hạn SLA. Nếu tổ chức chưa cấu hình lịch, hệ thống đọc theo UTC.
Vì sao điều này quan trọng
Ở Việt Nam (UTC+7), lúc 02
giờ UTC thì đã là ngày hôm sau theo giờ địa phương. Nếutoday() đọc theo giờ máy chủ, nó sẽ trả về ngày hôm qua trong suốt bảy tiếng mỗi ngày — và trả về một cách rất "hợp lý", nên sai sót kiểu này rất khó phát hiện. Hãy đặt múi giờ tổ chức ngay từ đầu.Ba hàm này chỉ có trên bề mặt chuỗi (nội dung email/webhook/tài liệu, câu lệnh agent). Chúng không có trong công thức trường biểu mẫu, vì một giá trị được tính lại mỗi lần lưu sẽ thay đổi liên tục nếu nó đọc đồng hồ.
Múi giờ thì khác với đồng hồ
date, datetime và date_diff dùng được ở mọi nơi, kể cả trong công thức biểu mẫu và công thức thực thể. Chúng chỉ đọc múi giờ — một thiết lập của tổ chức — chứ không đọc thời điểm chạy. Cùng một dữ liệu vào thì luôn cho cùng một kết quả, nên giá trị đã lưu không bị thay đổi mỗi lần lưu lại.
Lưu ý: đổi múi giờ của tổ chức sẽ khiến hệ thống tính lại những công thức thực thể có đọc múi giờ — đó là chủ đích, vì kết quả cũ đã được tính theo múi giờ trước đó.
Khoảng cách giữa hai ngày
date_diff(từ_ngày, đến_ngày, "đơn_vị") cho biết hai mốc thời gian cách nhau bao nhiêu, tính theo đến_ngày − từ_ngày:
{{ date_diff(input.created_at, now(), "days") }}| Đơn vị | Ý nghĩa |
|---|---|
"days" | Số ngày (mặc định nếu bỏ trống) |
"weeks" | Số tuần trọn vẹn (đúng bảy ngày) |
"months" | Số tháng trọn vẹn theo lịch |
"years" | Số năm trọn vẹn theo lịch |
"hours" | Số giờ |
"minutes" | Số phút |
"seconds" | Số giây |
-
Thứ tự tham số quan trọng.
date_diff(a, b)làb − a. Muốn biết "hồ sơ này đã tạo bao nhiêu ngày rồi", viếtdate_diff(created_at, now(), "days")— đảo lại sẽ ra số âm. -
Tham số là đường dẫn, không phải chuỗi trong ngoặc kép.
date_diff(now(), "created_at", "days")sẽ coi"created_at"là một đoạn văn bản, không đọc trường của bạn, và trả về giá trị trống. Chỉ đơn vị mới đặt trong ngoặc kép. (Hàm tổng hợpsum/avg/…thì ngược lại — xem mục 6 — nên rất dễ nhầm.) -
date_diffkhông đọc đồng hồ, nên nó dùng được ở mọi nơi, kể cả công thức trường biểu mẫu — miễn là cả hai mốc đều là dữ liệu đã lưu. -
daysđếm theo nhãn ngày trên lịch, không phải theo 24 giờ. Từ23:00hôm nay đến01:00ngày mai là 1 ngày (và 2 giờ). Đây là chủ ý: thời hạn được viết theo ngày trên lịch, còn thời lượng là đại lượng vật lý — hai cách hiểu này thật sự khác nhau. -
weeks,monthsvàyearsđếm số đơn vị trọn vẹn, bước qua mốc thôi chưa đủ. Từ 31/01 đến 01/02 là 0 tháng, phải đến 29/02 mới đủ 1 tháng. Đây chính là cách tính tuổi: người sinh 15/06/2000 đến ngày 14/06/2026 vẫn là 25 tuổi, sang 15/06 mới thành 26.{{ date_diff(ngay_sinh, today(), "years") }} -
Ngày kỷ niệm được tính đúng như
date_addcộng thêm, kể cả quy tắc lùi về cuối tháng ở mục dưới — nêndate_diffvàdate_addkhông bao giờ trả lời khác nhau về việc 31/01 rơi vào ngày nào của tháng 2.
Cộng thêm vào một ngày
date_add(giá_trị, số_lượng, "đơn_vị") cho ra ngày sau đó số_lượng đơn vị. Hàm này đồng thời là một bộ lọc, nên hai cách viết dưới đây là cùng một lệnh — dùng cách nào dễ đọc hơn:
{{ date_add(ngay_ky, 1, "years") }}
{{ today() | date_add(30) | date("dd/mm/yyyy") }}| Đơn vị | Ý nghĩa |
|---|---|
"days" | Số ngày (mặc định nếu bỏ trống) |
"weeks" | Số tuần (đúng bảy ngày) |
"months" | Số tháng theo lịch |
"years" | Số năm theo lịch |
"hours" | Số giờ |
"minutes" | Số phút |
"seconds" | Số giây |
- Cuối tháng bị kẹp lại.
date_add("2026-01-31", 1, "months")là2026-02-28, không phải2026-03-03. Và vì bị kẹp nên cộng rồi trừ lại không quay về chỗ cũ:+1 thángrồi−1 thángtừ ngày 31/01 cho ra 28/01. Mọi phần mềm lịch đều như vậy — ngày 29, 30 và 31 tháng 1 đều rơi vào cùng một ngày của tháng 2, nên không có đường về. - Số âm là trừ.
date_add(han_nop, -3)nghĩa là "3 ngày trước hạn". Không có hàmdate_subriêng. - Kết quả giữ nguyên dạng của đầu vào. Ngày vẫn là ngày (
2026-03-02), ngày-giờ vẫn là ngày-giờ — nên lưu thẳng lại được vào đúng loại trường đã sinh ra nó, và nối tiếp với| date("dd/mm/yyyy")để hiển thị. - Đừng cộng giờ/phút/giây vào một giá trị
date. Một ngày không có giờ, nêndate_add(ngay_nhan, 4, "hours")cho ra2026-01-01T04:00:00— hiển thị thì được, nhưng không lưu được vào trườngdatetime(trường đó cần có múi giờ) và trường tính toán sẽ lặng lẽ để trống. Hãy bắt đầu từnow()hoặc từ một trườngdatetime. - Viết đơn vị trực tiếp trong ngoặc kép. Nếu lấy đơn vị từ một trường dữ liệu, một giá trị sai sẽ làm hỏng toàn bộ nội dung đang kết xuất, còn giá trị trống sẽ âm thầm bị hiểu là
"days". date_addkhông đọc đồng hồ, nên dùng được ở mọi nơi, kể cả trường tính toán — và đó chính là chỗ nó hữu ích nhất:date_add(ngay_ky, 1, "years")cho ngày hết hiệu lực.
Cộng thêm ngày làm việc
add_business_days(giá_trị, n) cho ra ngày cách giá_trị đúng n ngày làm việc, tính theo Lịch làm việc của tổ chức: các ngày làm việc trong tuần, trừ đi ngày nghỉ lễ. Giống date_add, hàm này cũng là một bộ lọc, và n âm là đếm lùi:
{{ add_business_days(created_at, 7) }}
{{ now() | add_business_days(-7) }}- Mỗi bước dừng ở ngày làm việc kế tiếp. Từ thứ Bảy,
+1là thứ Hai và-1là thứ Sáu.0là chính giá trị đó, kể cả khi đó là cuối tuần hay ngày nghỉ lễ. - Giá trị có giờ thì giữ nguyên giờ.
now() | add_business_days(-7)là cùng giờ đó của bảy ngày làm việc trước, cùng độ lệch múi giờ; một ngày thì vẫn là một ngày. - Tổ chức chưa cấu hình lịch thì làm việc từ thứ Hai đến thứ Sáu, không có ngày nghỉ lễ.
- Chỉ dùng trong các bước của quy trình. Lịch được đọc từ thiết lập của tổ chức khi bước chạy. Hàm không có trong trường biểu mẫu tính toán và công thức thực thể (trình soạn thảo từ chối ở đó, như với
now()), cũng không có trong câu lệnh của tác tử. Trong một phép gán Set Variable không đếm được, biến để trống chứ không bị đếm theo những ngày trong tuần mà không ai xác nhận. - Vì sao đây không phải một đơn vị của
date_add?date_addkhông đọc đồng hồ cũng không đọc lịch, nên nó dùng được ở mọi nơi và luôn cho cùng một kết quả mỗi lần tính lại. Ngày làm việc phụ thuộc vào một lịch mà tổ chức có thể sửa, nên đếm chúng là một hàm riêng với phạm vi hẹp hơn.
4. Phép tính & điều kiện
Phép tính dùng + - * / % và dấu ngoặc:
{{ (input.price * input.qty) | round(2) }}Điều kiện nội dòng — A if điều_kiện else B:
{{ "Ưu tiên" if input.amount > 1000 else "Tiêu chuẩn" }}So sánh: == != < <= > >=, kết hợp với and / or / not.
Trên bề mặt giá trị (Set Variable, trường tính toán) kết quả giữ nguyên kiểu:
{{ a - b }}lưu một số mà bạn có thể tính tiếp.
5. Vòng lặp & khối (chỉ bề mặt chuỗi)
Trên nội dung email/webhook/tài liệu và câu lệnh agent bạn có thể lặp và rẽ nhánh bằng thẻ khối.
Vòng lặp trên danh sách:
{% for line in nodes.collect.output.items %}
- {{ line.name }} × {{ line.qty }} = {{ line.qty * line.price | number }}
{% endfor %}Khối điều kiện:
{% if input.amount > 1000 %}
Đơn hàng này cần quản lý duyệt.
{% else %}
Tự động duyệt.
{% endif %}Thẻ khối không có trên bề mặt giá trị (trường tính toán trả về một giá trị đơn, không phải một khối được kết xuất).
6. Trường biểu mẫu tính toán
Trường tính toán (computed) tính giá trị của nó từ các trường cùng cấp. Nhập công thức trong cài đặt của trường; nó được tính trên máy chủ — trực tiếp khi bạn điền biểu mẫu, và chính thức khi gửi.
Cột theo dòng — tham chiếu các trường cùng dòng:
so_tien = so_luong * don_giaTổng cấp cao nhất — tổng hợp một mục lặp bằng sum/avg/min/max/count(list, "expr"):
tong = sum(items, "so_tien")Tham số thứ hai là một biểu thức theo từng phần tử, không chỉ là tên cột — một trường đơn, một đường dẫn lồng nhau (sum(items, "line.amount")), hoặc phép tính trên các ô cùng dòng — nên bạn có thể tính tổng một giá trị dòng đã tính toán trực tiếp, không cần cột phụ:
tong = sum(items, "so_luong * don_gia")Ví dụ hóa đơn hoàn chỉnh:
| Trường | Công thức |
|---|---|
items[].so_tien (mỗi dòng) | so_luong * don_gia |
tong (cấp cao nhất) | sum(items, "so_tien") |
vat (cấp cao nhất) | tong * 0.1 |
tong_cong (cấp cao nhất) | tong + vat |
Hàm tổng hợp bỏ qua ô trống/không phải số; danh sách rỗng cho tổng bằng 0. Điều kiện nội dòng cũng dùng được:
tier = "bulk" if sum(items, "so_luong") >= 100 else "standard"Trường tính toán không bao giờ bắt buộc và có thể trống một cách hợp lệ (ví dụ tổng hợp rỗng). Máy chủ ghi đè mọi giá trị trình duyệt gửi lên, nên không thể bị giả mạo.
7. Tính năng nào dùng ở đâu
| Tính năng | Bề mặt chuỗi (email/webhook/tài liệu/câu lệnh) | Bề mặt giá trị (Set Variable / tính toán / rẽ nhánh) |
|---|---|---|
Nội suy {{ path }} | ✅ | ✅ |
| Truy cập trường / chỉ số | ✅ | ✅ |
Bộ lọc (date, round, upper, …) | ✅ | ✅ |
| Phép tính | ✅ (kết xuất ra văn bản) | ✅ (giữ số) |
if/else nội dòng, so sánh | ✅ | ✅ |
Khối {% for %} / {% if %} | ✅ | — |
now() / today() / year() | ✅ (theo múi giờ tổ chức) | — (giữ tính tất định) |
date_diff(...) | ✅ | ✅ (không đọc đồng hồ) |
date_add(...) | ✅ | ✅ (không đọc đồng hồ) |
add_business_days(...) | ✅ (bước quy trình) | ✅ trong Set Variable và các bước quy trình khác — không có trong trường tính toán |
Hàm tổng hợp sum(...) v.v. | ✅ | ✅ |
Bảng này nói về những ô có nhận mẫu. Một vài ô giá trị trong trình thiết kế cố ý không nhận mẫu — xem gạch đầu dòng cuối mục 8.
8. Điểm cần lưu ý
- Giá trị tùy chọn — bảo vệ bằng
| default(...)để một giá trị thiếu không làm trống cả dòng:{{ x | default(0) | round(2) }}. mmso vớiMM— chữ thường là tháng, chữ hoa là phút.dd-mm-yyyy HH:MMlà thứ bạn thường cần.| labelbên trong vòng lặp — một{{ item.kind | label }}cục bộ trong vòng lặp không thể tra cấu trúc danh sách chọn, nên nó quay về giá trị thô. Các token label ở cấp cao nhất thì bình thường.- Đường dẫn không phân giải được thì cho giá trị TRỐNG, không báo lỗi. Gõ sai một đường dẫn (
created_atthay vìinput.item.created_at), hoặc đặt đường dẫn vào trong ngoặc kép, sẽ vượt qua khâu kiểm tra khi xuất bản rồi ghi ra ô trống lúc chạy. Nếu một trường ra trống mà bạn không hiểu vì sao, hãy nghi ngờ đường dẫn trước tiên — dùng bộ chọn biến thay vì gõ tay. now()/today()/year()— có trong mẫu quy trình, không có trong công thức trường biểu mẫu (để giá trị của trường ổn định qua các lần tính lại). Chúng đọc theo múi giờ của tổ chức; nếu chưa đặt lịch làm việc thì đọc theo UTC.- Khóa trường — chỉ dạng định danh (không khoảng trắng, không gạch ngang, không bắt đầu bằng chữ số). Trình thiết kế bắt buộc điều này; hãy đổi tên trường cũ nào vi phạm.
- Lỗi chặn xuất bản — một bộ lọc gõ sai (
| daet) hoặc cú pháp hỏng sẽ được báo trong trình soạn thảo và ngăn xuất bản/lưu cho tới khi sửa. - Có ô giá trị cố ý không nhận mẫu. Ô Đơn vị đích (Destination unit) của bước Bản ghi thực thể chỉ nhận đúng hai nguồn giá trị: Giá trị tĩnh (Literal) — chọn trên cây đơn vị — và Tham chiếu (Reference) tới một biến quy trình đang giữ id đơn vị. Không có lựa chọn Biểu thức (Expression), và đó là chủ ý: một mẫu kết xuất ra văn bản, mà văn bản thì không bao giờ là một id đơn vị. Gõ
{{ vars.don_vi }}vào đó sẽ tạo ra một đích không phân giải được — và ở ô này, khác với quy tắc “đường dẫn không phân giải được thì cho giá trị trống” ở trên, hậu quả không phải là một ô trống lặng lẽ mà là lượt chạy bị dừng, ở mọi lần chạy. Nếu đơn vị chỉ biết được lúc chạy, hãy tính nó ở một bước Set Variable rồi trỏ Tham chiếu vào biến đó.