Fórmulas
Várias partes do Fácil123 aceitam uma fórmula no lugar de um valor fixo: critérios de execução, impressões e etiquetas personalizadas, campos personalizados preenchidos automaticamente, critérios de inscrição e de roteamento em automações e validações personalizadas.
A linguagem das fórmulas é o JSON Logic — um jeito de escrever regras como um pequeno JSON. Este artigo explica a sintaxe e lista todas as operações disponíveis, incluindo as que são específicas do Fácil123.
Onde você usa fórmulas
- Campos personalizados com preenchimento por fórmula
- Impressões personalizadas e etiquetas personalizadas em elementos do tipo Fórmula
- Validações personalizadas — tanto no critério de execução quanto na validação em si
- Automações — no critério de inscrição e em comandos como Se então
A sintaxe é a mesma em todos esses lugares. O que muda é qual é a entidade do contexto (uma venda, uma pessoa, uma produção…) e, portanto, quais nomes de atributo funcionam em attr.
Como uma fórmula funciona
Toda fórmula é um objeto JSON com uma única chave — o nome da operação — e um array com os argumentos. Esta fórmula compara dois valores e devolve true:
{ "==": [1, 1] }
Resultado:
true.
Uma fórmula pode ser argumento de outra. É assim que você monta expressões maiores — cada operação devolve um valor, que vira entrada para a operação de fora:
{
"and": [
{ ">": [{ "attr": "invoice.total" }, 0] },
{ "==": [{ "attr": "invoice.status" }, "A"] }
]
}
Resultado:
truequando o total da venda é maior que zero E a situação é"A"(autorizada);falsecaso contrário.
Nomes dos atributos
O primeiro argumento de attr é um caminho começando pela entidade do contexto:
- Em venda (invoice):
invoice.total,invoice.status,invoice.note,invoice.emitted_at,invoice.recipient.name,invoice.recipient.kind,invoice.recipient.cnpj,invoice.seller.name,invoice.licensee.name - Em pessoa (person):
person.name,person.kind,person.cpf,person.cnpj,person.act_as_customer - Em produção (production), produto (product), compra (purchase), transação (transaction): mesmo padrão, sempre começando pelo nome da entidade
Os editores do Fácil123 mostram a lista de atributos disponíveis quando você está montando a fórmula — use-a como referência ao invés de memorizar nomes.
Campos personalizados aparecem com o prefixo cf_. Por exemplo, um campo personalizado de venda com nome interno vendedor_indicador é acessado como invoice.cf_vendedor_indicador.
Duas formas equivalentes
Em fórmulas prontas você vai encontrar tanto {"attr": "invoice.total"} (string) quanto {"attr": ["invoice.total"]} (array). As duas dão o mesmo resultado — a linguagem envolve um argumento solitário em array automaticamente. A forma em array é obrigatória quando a operação recebe dois ou mais argumentos (como values_of e format_mask_text).
Os exemplos deste artigo usam a forma em string sempre que a operação recebe um argumento só — fica mais enxuto de ler.
Operações padrão
Estas são as operações do JSON Logic e funcionam em qualquer lugar. A lista completa está em jsonlogic.com/operations — abaixo vão as mais usadas no Fácil123.
| Operação | O que faz |
|---|---|
==, != |
Comparação de igualdade |
>, >=, <, <= |
Comparação numérica ou de datas |
and, or, ! |
Lógica booleana |
if |
Condicional: {"if": [condição, então, senão]} |
in |
Verifica se um valor está em uma lista ou substring em texto |
var |
Lê um valor dos dados passados à fórmula |
cat |
Concatena textos |
+, -, *, /, % |
Aritmética |
min, max |
Menor/maior de uma lista |
merge |
Junta arrays em um só |
missing |
Lista quais variáveis de um conjunto não foram informadas |
all |
Verdadeiro se todos os itens do array satisfazem a condição |
some |
Verdadeiro se pelo menos um item satisfaz a condição |
none |
Verdadeiro se nenhum item satisfaz a condição |
reduce |
Reduz um array a um único valor, acumulando item a item |
Iterando sobre coleções
all, some, none e reduce são o que você usa para validar os itens de uma venda, as parcelas, os insumos de uma produção, etc. A coleção geralmente vem de um values_of.
Dentro da condição de all, some e none, cada item vira o contexto — você acessa o atributo do item atual com {"var": ".nome_do_atributo"} (repare no ponto inicial). O exemplo abaixo devolve true se todos os itens da venda têm quantidade maior que zero:
{
"all": [
{ "values_of": ["invoice.invoice_products", "quantity"] },
{ ">": [{ "var": ".quantity" }, 0] }
]
}
Resultado:
truequando todo item temquantity > 0;falsese algum item tiver zero ou negativo.
Já o reduce leva três argumentos — coleção, expressão de acumulação e valor inicial — e expõe {"var": "accumulator"} (valor acumulado) e {"var": "current"} (item atual). Este exemplo conta quantas parcelas a venda tem:
{
"reduce": [
{ "values_of": ["invoice.invoice_bills", "expire_at"] },
{ "+": [1, { "var": "accumulator" }] },
0
]
}
Resultado:
3numa venda com três parcelas;0numa venda sem parcelas.
Operações do Fácil123
Estas operações são adicionadas pelo Fácil123 e não existem no JSON Logic padrão.
Dados do documento
| Operação | O que faz |
|---|---|
attr |
Lê um atributo da entidade atual: {"attr": "invoice.total"}. O caminho pode navegar por associações: {"attr": "invoice.recipient.cnpj"} |
values_of |
Lê uma coleção — por exemplo, os valores de um atributo em cada item da venda: {"values_of": ["invoice.invoice_products", "quantity"]} |
memory |
Lê um valor que ficou em memória de um passo anterior da automação: {"memory": ["chave"]} |
current_user_id / more.current_user_id |
Devolve o id do usuário que disparou a ação |
more.current_weekday |
Devolve o dia da semana atual por extenso (ex.: "Monday") |
Verificação de valor
| Operação | O que faz |
|---|---|
is_empty |
Verdadeiro quando o valor é null: {"is_empty": [{"attr": "invoice.note"}]} |
is_filled |
Verdadeiro quando o valor está preenchido — inverso do is_empty |
contain |
Verdadeiro quando um texto contém outro: {"contain": ["abacaxi", "caxi"]} |
start_with |
Verdadeiro quando um texto começa com outro: {"start_with": [{"attr": "invoice.recipient.name"}, "SA "]} |
Texto
| Operação | O que faz |
|---|---|
cat |
Concatena: {"cat": ["Cliente: ", {"attr": "invoice.recipient.name"}]} (essa é padrão, mas vale lembrar) |
split |
Divide um texto por um separador: {"split": ["12/06/2026", "/"]} → ["12","06","2026"] |
array.at |
Pega o item na posição N de um array: {"array.at": [{"split": ["12/06/2026", "/"]}, 0]} → "12" |
rjust |
Preenche à esquerda até atingir um tamanho: {"rjust": [42, 5, "0"]} → "00042" |
fmt |
Formata número com N casas decimais: {"fmt": [3.1, 2]} → "3,10" |
format_mask_text |
Aplica uma máscara a um texto. Segundo argumento é a máscara (ex.: "##.###.###/####-##" para CNPJ) |
strftime |
Formata uma data no fuso do licenciado: {"strftime": [{"attr": "invoice.emitted_at"}, "%d/%m/%Y"]} |
Conversão
| Operação | O que faz |
|---|---|
as_time |
Converte um texto para data/hora: {"as_time": ["2026-04-16"]} |
as_decimal |
Converte um texto para número decimal: {"as_decimal": ["3.14"]} |
Tempo — valores
| Operação | O que faz |
|---|---|
time.now |
Agora: {"time.now": []} |
time.from_now |
Data futura, informando dias/hora/minuto: {"time.from_now": [7, 8, 0]} — daqui a 7 dias às 08h00 |
time.days_after_today / time.days_before_today |
O dia inteiro N dias depois/antes de hoje — útil para comparar com datas que não têm hora |
time.seconds_ago, time.seconds_from_now |
Um instante N segundos no passado/futuro |
time.minutes_ago, time.minutes_from_now |
Idem em minutos |
time.hours_ago, time.hours_from_now |
Idem em horas |
seconds |
Converte um número em uma duração em segundos, para somar em datas |
time.+ |
Soma uma duração a uma data: {"time.+": [{"time.now": []}, {"seconds": [3600]}]} |
time |
Atalho para intervalos do tipo "today", "yesterday", "tomorrow", "this_week", "last_week", "this_month", "last_month", "this_year", "last_year" |
Tempo — comparação
As comparações de tempo (time.equal, time.different, time.less_than, time.less_than_or_equal, time.greater_than, time.greater_than_or_equal) funcionam como os operadores comuns de comparação, mas aceitam intervalos no segundo argumento. Por exemplo, time.equal com um intervalo devolve verdadeiro quando a data cai dentro do intervalo — é o que permite comparar "a data é hoje" sem se importar com a hora.
{ "time.equal": [{ "attr": "invoice.emitted_at" }, { "time": ["today"] }] }
Resultado:
truequandoemitted_atcaiu em qualquer hora do dia de hoje;falsecaso contrário.
Caracteres de controle (impressão térmica)
Usadas em elementos de Fórmula de impressões e etiquetas para enviar comandos à impressora.
| Operação | O que faz |
|---|---|
stx |
Caractere STX (início de texto, \x02) |
cr |
Caractere CR (retorno de carro, \x0D) |
lf |
Caractere LF (quebra de linha, \x0A) |
esc |
Caractere ESC (escape, \x1B) |
O que a fórmula deve devolver
O tipo de valor depende de onde a fórmula é usada:
- Validações personalizadas, critérios de execução, critérios de inscrição em automação, blocos "Se então": a fórmula devolve um booleano.
trueautoriza a ação (a validação passa, a automação inscreve, o fluxo segue pelo "então");falsebarra. - Campo personalizado por fórmula: a fórmula devolve o valor a ser gravado no campo (texto, número, data — conforme o tipo do campo).
- Elemento Fórmula de impressão/etiqueta: a fórmula devolve o texto a ser impresso.
Exemplos práticos
Concatenar nome e CNPJ formatado
Em uma etiqueta personalizada de venda, para imprimir "Indústria XYZ — 12.345.678/0001-90":
{
"cat": [
{ "attr": "invoice.recipient.name" },
" — ",
{ "format_mask_text": [{ "attr": "invoice.recipient.cnpj" }, "##.###.###/####-##"] }
]
}
Resultado:
"Indústria XYZ — 12.345.678/0001-90".
Obrigatoriedade do vendedor
Em uma validação personalizada de venda, barrar a gravação quando o vendedor não foi informado:
{ "is_filled": [{ "attr": "invoice.seller_id" }] }
Resultado:
true(venda válida) quandoseller_idestá preenchido;false(barra a gravação) quando está vazio.
Restringir movimentação de etapa a usuários específicos
Só permitir que a venda seja salva se quem está salvando está em uma lista curta de usuários autorizados. O if com current_user_id == null é importante: jobs automáticos e rotinas de background não têm usuário — sem esse escape a validação barraria tudo que não é disparado manualmente.
{
"if": [
{ "==": [{ "current_user_id": [] }, null] },
true,
{ "in": [{ "current_user_id": [] }, [4748, 10069, 10067]] }
]
}
Resultado:
truepara rotinas automáticas ou para usuários cujo id está na lista;falsepara qualquer outro usuário.
Obrigar que a venda tenha pelo menos uma parcela
Usando reduce para contar as parcelas (invoice_bills) e comparando com zero:
{
">": [
{
"reduce": [
{ "values_of": ["invoice.invoice_bills", "expire_at"] },
{ "+": [1, { "var": "accumulator" }] },
0
]
},
0
]
}
Resultado:
truequando há pelo menos uma parcela;false(barra) quando não há nenhuma.
Prazo de vencimento limitado pelo cadastro do cliente
Cada cliente tem um campo personalizado cf_prazo_maximo_dias com o prazo máximo que ele pode financiar. A validação barra a venda se qualquer parcela vencer depois desse limite, contado a partir da emissão:
{
"all": [
{ "values_of": ["invoice.invoice_bills", "expire_at"] },
{
"time.less_than_or_equal": [
{ "var": ".expire_at" },
{
"time.+": [
{ "attr": "invoice.emitted_at" },
{ "seconds": [{ "*": [{ "attr": "invoice.recipient.cf_prazo_maximo_dias" }, 86400] }] }
]
}
]
}
]
}
Resultado:
truequando todas as parcelas vencem até o prazo máximo;falsequando alguma parcela extrapola. Osecondscomdias * 86400converte dias em uma duração somável aoemitted_at.
Campo personalizado calculado: dia da semana da emissão
Preencher um campo do tipo Texto Curto em venda com o dia da semana em que ela foi emitida:
{ "strftime": [{ "attr": "invoice.emitted_at" }, "%A"] }
Resultado:
"Monday","Tuesday", etc. — o dia da semana da emissão.
Erros comuns
Armadilhas que aparecem em fórmulas antigas e que quebram silenciosamente — sem erro visível, mas com resultado diferente do esperado.
Caminho de attr que não existe vira null
Se o caminho está errado, attr devolve null em vez de dar erro. O efeito é que a sua validação parece funcionar mas na verdade está comparando null com tudo.
Os escorregões mais comuns:
- Misturar português com inglês:
invoice.loteem vez deinvoice.lot(ou o nome real do campo). - Começar pela entidade errada:
seller.seller_idem vez deinvoice.seller_idquando o contexto é venda. - Tratar coleção como associação 1:1:
invoice.product.lotnão existe — uma venda teminvoice_products(coleção), nãoproduct. Para ler valores de itens usevalues_of.
Quando desconfiar, substitua a fórmula inteira por apenas o attr em questão e veja se o valor retornado faz sentido.
Usar values_of para um atributo único
values_of é para coleções (itens da venda, parcelas, materiais da produção). Para ler um atributo único — mesmo passando por associações — use attr.
Errado:
{ "==": [{ "values_of": ["invoice.seller_id"] }, null] }
Certo:
{ "is_empty": [{ "attr": "invoice.seller_id" }] }
or com vários != é sempre verdadeiro
Um valor não pode ser diferente de A e diferente de B e diferente de C ao mesmo tempo — algum desses != vai ser verdadeiro para qualquer combinação. Esta fórmula, que parece querer dizer "não é nenhuma dessas etapas", na prática devolve true para qualquer venda:
{
"or": [
{ "!=": [{ "attr": "invoice.invoicelane_id" }, 7773] },
{ "!=": [{ "attr": "invoice.invoicelane_id" }, 7774] },
{ "!=": [{ "attr": "invoice.invoicelane_id" }, 10170] }
]
}
O jeito correto de dizer "não está em nenhuma dessas" é usar in negado:
{ "!": { "in": [{ "attr": "invoice.invoicelane_id" }, [7773, 7774, 10170]] } }
Se a intenção era o oposto ("está em todas" — o que normalmente não faz sentido), troque or por and.
Perguntas e respostas
1 - Como descubro o nome do atributo para usar em attr?
R: O editor do Fácil123 mostra a lista de atributos da entidade atual e das entidades associadas — use essa lista como referência. Associações ficam acessíveis separando por ponto (ex.: invoice.recipient.cnpj). Para um campo personalizado, use o prefixo cf_ com o nome interno do campo (ex.: invoice.cf_vendedor_indicador).
2 - Minha fórmula está dando erro. Como eu depuro?
R: Comece isolando. Troque a fórmula por apenas {"attr": "invoice.total"} (ou o atributo em questão) para confirmar que o campo existe com o nome que você está usando. Depois monte a expressão de dentro pra fora, testando cada pedaço. Se uma subexpressão devolver null inesperado, provavelmente o caminho do attr está errado.
3 - Posso usar qualquer operação em qualquer lugar?
R: Quase. As operações de tempo que dependem do fuso do licenciado (strftime, time.from_now, time.days_after_today, etc.) precisam de um licenciado no contexto — praticamente sempre há um. Já memory só existe em automações, quando um passo anterior deixou valor em memória. E attr precisa de uma entidade no contexto, então não funciona em lugares totalmente genéricos.
4 - Qual a diferença entre == e time.equal?
R: == compara exatamente. time.equal sabe lidar com intervalos no segundo lado — por isso comparar invoice.emitted_at com {"time": ["today"]} usando time.equal funciona (o dia inteiro é um intervalo), enquanto == devolveria falso porque a hora não bate.