Bỏ qua để đến nội dung

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 {{ … }} — 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.

Ở 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".

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 }}

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.

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á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 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

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.

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.

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ồ.

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)b − a. Muốn biết “hồ sơ này đã tạo bao nhiêu ngày rồi”, viết date_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ợp sum/avg/… thì ngược lại — xem mục 6 — nên rất dễ nhầm.)

  • date_diff khô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:00 hôm nay đến 01:00 ngà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, monthsyears đế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_add cộng thêm, kể cả quy tắc lùi về cuối tháng ở mục dưới — nên date_diffdate_add khô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.

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")2026-02-28, không phải 2026-03-03. Và vì bị kẹp nên cộng rồi trừ lại không quay về chỗ cũ: +1 tháng rồi −1 tháng từ 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àm date_sub riê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ên date_add(ngay_nhan, 4, "hours") cho ra 2026-01-01T04:00:00 — hiển thị thì được, nhưng không lưu được vào trường datetime (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ường datetime.
  • 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_add khô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.

Phép tính dùng + - * / % và dấu ngoặc:

{{ (input.price * input.qty) | round(2) }}

Điều kiện nội dòngA 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)

Phần tiêu đề “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).

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_gia

Tổ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.

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ồ)
Hàm tổng hợp sum(...) v.v.
  • 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) }}.
  • mm so với MM — chữ thường là tháng, chữ hoa là phút. dd-mm-yyyy HH:MM là thứ bạn thường cần.
  • | label bê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_at thay 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.