Skip to main content
ExampleIntermediate1 min readUpdated Jul 2, 2026
agentllmopenaiappointmentsschedulingservicesportuguese

Agente agendador

Scheduling assistant that fetches company services, explains booking rules in Portuguese, checks availability, and creates events for a salon-style appointment flow.

Download ZIP · 6.7 KB

Included Dependencies

info

The downloadable ZIP includes all dependencies listed below. All cross-references are automatically linked during import.

Custom types: agenda template

The agent stores all of its scheduling data in four custom types, whose definitions are bundled in the ZIP (custom_types.json):

TypeSlugUsed for
Servicesct:servicesBookable services with a price and duration
Working Hoursct:working_hoursPer-employee weekly schedule (one row per weekday)
Eventsct:eventsAppointments the agent creates and looks up
Employee Periodsct:employee_periodsVacations and per-date availability overrides

Services: Agendador Environment

Seed these services alongside the agent so its preActions can list available offerings and prices. The ZIP seeds them as ct:services documents (custom_type_docs.json):

#NameDetails
1Corte de cabeloR$ 50, 30 min
2BarbaR$ 35, 20 min
3Corte + BarbaR$ 75, 45 min
4Hidratação capilarR$ 60, 40 min
5SobrancelhaR$ 25, 15 min

Working hours seed

Seven ct:working_hours rows define the sample weekly schedule the agent quotes to contacts: Monday to Friday 09:00–18:00, Saturday 09:00–13:00, Sunday off.

warning

The seeded services and working-hours rows ship with an <employee-id> placeholder. After importing, edit each seeded document so employeeId points to one of your employees — the agent looks up an employee in your company and filters both lists by that employee's ID.

Full JSON

Click to expand
{
"description": "Scheduling assistant that fetches company services, explains booking rules in Portuguese, checks availability, and creates events for a salon-style appointment flow.",
"tags": [
"agent",
"llm",
"openai",
"appointments",
"scheduling",
"services",
"portuguese"
],
"linkedExamples": [
{
"type": "services",
"variant": "agendador-environment",
"relationship": "Seed these services alongside the agent so its preActions can list available offerings and prices."
}
],
"document": {
"name": "Agente agendador",
"assistantType": "llm",
"options": {
"log_level": "debug"
},
"platform": {
"openAi": {
"model": "gpt-4.1",
"temperature": 1,
"maxCharsPerMessage": 1024,
"preActions": [
{
"condition": {
"expr": "true",
"onFailure": "keep"
},
"action": {
"actionType": "actions/security/auth/jwt/generate",
"actions/security/auth/jwt/generate": {
"is_advanced": false,
"duration": 300,
"ips": []
}
}
},
{
"condition": {
"expr": "true",
"onFailure": "keep"
},
"action": {
"actionType": "actions/state/session/memory/mutate",
"actions/state/session/memory/mutate": {
"operation": "set",
"key": "apiToken",
"value": {
"expr": "step(0).jwt",
"onFailure": "keep"
}
}
}
},
{
"condition": {
"expr": "true",
"onFailure": "keep"
},
"action": {
"actionType": "actions/state/session/memory/mutate",
"actions/state/session/memory/mutate": {
"operation": "set",
"key": "apiUrl",
"value": {
"expr": "\"https://api.autotalk.io/v1\"",
"onFailure": "keep"
}
}
}
},
{
"condition": {
"expr": "true",
"onFailure": "keep"
},
"action": {
"actionType": "actions/data/company/resource/search",
"actions/data/company/resource/search": {
"dynadataItemType": "employees",
"filter": {
"expr": "{\"aclInfo.tenantId\": company._id}",
"onFailure": "throw"
},
"limit": 1,
"skip": 0
}
}
},
{
"condition": {
"expr": "true",
"onFailure": "keep"
},
"action": {
"actionType": "actions/state/session/memory/mutate",
"actions/state/session/memory/mutate": {
"operation": "set",
"key": "employee",
"value": {
"expr": "step(3).data[0]",
"onFailure": "keep"
}
}
}
},
{
"condition": {
"expr": "true",
"onFailure": "keep"
},
"action": {
"actionType": "actions/data/company/resource/search",
"actions/data/company/resource/search": {
"dynadataItemType": "ct:working_hours",
"filter": {
"expr": "{\"employeeId\": string(employee._id)}",
"onFailure": "throw"
},
"limit": 7,
"skip": 0
}
}
},
{
"condition": {
"expr": "true",
"onFailure": "keep"
},
"action": {
"actionType": "actions/state/session/memory/mutate",
"actions/state/session/memory/mutate": {
"operation": "set",
"key": "workingHoursRows",
"value": {
"expr": "step(5).data",
"onFailure": "keep"
}
}
}
},
{
"condition": {
"expr": "true",
"onFailure": "keep"
},
"action": {
"actionType": "actions/state/session/memory/mutate",
"actions/state/session/memory/mutate": {
"operation": "set",
"key": "today",
"value": {
"expr": "workingHoursRows.filter(row, row.dayOfWeek == getDayOfWeek(timestamp(now()), \"America/Sao_Paulo\"))[0]",
"onFailure": "keep"
}
}
}
},
{
"condition": {
"expr": "true",
"onFailure": "keep"
},
"action": {
"actionType": "actions/state/session/memory/mutate",
"actions/state/session/memory/mutate": {
"operation": "set",
"key": "now",
"value": {
"expr": "now()",
"onFailure": "keep"
}
}
}
},
{
"condition": {
"expr": "true",
"onFailure": "keep"
},
"action": {
"actionType": "actions/state/session/memory/mutate",
"actions/state/session/memory/mutate": {
"operation": "set",
"key": "workingTime",
"value": {
"expr": "{\n \"openTime\": zoned_datetime(format_datetime(now(), \"YYYY-MM-DD\"), today.startTime, \"America/Sao_Paulo\"),\n \"closeTime\": zoned_datetime(format_datetime(now(), \"YYYY-MM-DD\"), today.endTime, \"America/Sao_Paulo\")\n}",
"onFailure": "keep"
}
}
}
},
{
"condition": {
"expr": "true",
"onFailure": "keep"
},
"action": {
"actionType": "actions/data/company/resource/search",
"actions/data/company/resource/search": {
"dynadataItemType": "ct:services",
"filter": {
"expr": "{\"employeeId\": string(employee._id)}",
"onFailure": "throw"
},
"limit": 50,
"skip": 0
}
}
},
{
"condition": {
"expr": "true",
"onFailure": "keep"
},
"action": {
"actionType": "actions/state/session/memory/mutate",
"actions/state/session/memory/mutate": {
"operation": "set",
"key": "services",
"value": {
"expr": "step(10).data",
"onFailure": "keep"
}
}
}
}
],
"systemRoleMessages": [
{
"expr": "\"Voce e um assistente virtual do \" + employee.name + \", você é capaz de fazer agendamentos, voce esta conversando com um contato chamado '\" + contact.name + \"', se recuse a responder qualquer coisa não relacionada ao horário de atendimento, o endereço de atendimento, e os agendamentos do contato. Caso seja um assunto que você não pode tratar, chame a função 'finaliza_atendimento'\"",
"onFailure": "throw"
},
{
"expr": "\"O horário de funcionamento semanal é: \" + toon_encode(workingHoursRows)",
"onFailure": "throw"
},
{
"expr": "\"Os serviços prestados são \\n\" + toon_encode(services)",
"onFailure": "throw"
},
{
"expr": "\"Agora são \" + format_datetime(now(), \"DD/MM/YYYY HH:mm\") + \", hoje é \" + format_datetime(now(), \"dddd\", \"America/Sao_Paulo\", \"pt-BR\") + \", \" +\n (today.isDayOff ? ('estamos fechados') : \" e agora são '\" + format_datetime(now(), \"HH:mm\", \"America/Sao_Paulo\", \"pt-BR\") + \", então \" + (is_before(now(), workingTime.openTime) ? \"ainda não estamos abertos\" : (is_after(now(), workingTime.closeTime) ? \"já estamos fechados\" : \"estamos abertos\"))) + \"!\"",
"onFailure": "throw"
},
{
"expr": "\"Antes de registrar o agendamento, descreva o agendamento em detalhes, com o preço total, se o contato aprovar chame a 'faz_agendamento'.\"",
"onFailure": "keep"
},
{
"expr": "\"Use a moeda 'BRL', mas utilize a notação humana. Para 'BRL' use 'R$' por exemplo.\"",
"onFailure": "keep"
},
{
"expr": "\"O fuso horário do contato é America/Sao_Paulo -0300, o fuso horário do estabelecimento é America/Sao_Paulo -0300\"",
"onFailure": "keep"
},
{
"expr": "\"Seja gentil, use emojis\"",
"onFailure": "keep"
},
{
"expr": "\"Caso você perceba que o contato \" + contact.name + \" está querendo falar diretamente com o profissional \" + employee.name + \" ou querendo tratar de uma questão pessoal, finalize o atendimento chamando a função 'finaliza_atendimento'\"",
"onFailure": "throw"
},
{
"expr": "\"Caso o contato queira marcar um agendamento envie para ele a lista de serviços disponíveis, não envie o ID do serviço.\"",
"onFailure": "keep"
},
{
"expr": "\"Não utilize Markdown nas respostas\"",
"onFailure": "keep"
}
],
"actions": [],
"tools": [
{
"type": "function",
"function": {
"name": "faz_agendamento",
"description": "Marca um agendamento de um contato",
"parameters": [
{
"name": "horario",
"type": "string",
"description": "\"Horario do agendamento, deve ser no formato ISO 8601, 'YYYY-MM-DDTHH:mm:ssZ', exemplo: 2014-09-08T08:02:17-05:00\"",
"isRequired": true
},
{
"name": "servicos",
"type": "array",
"description": "IDs do serviços que serão feitos no agendamento",
"arrayItems": {
"name": "id_do_servico",
"type": "string",
"description": "ID do serviço a ser realizado no agendamento",
"isRequired": true
},
"isRequired": true
}
],
"actions": [
{
"condition": {
"expr": "true",
"onFailure": "keep"
},
"action": {
"actionType": "actions/network/http/request/send",
"actions/network/http/request/send": {
"method": "POST",
"url": {
"expr": "apiUrl + \"/dynadata/type/ct%3Aevents/create\"",
"onFailure": "keep"
},
"headers": [
{
"key": {
"expr": "\"Content-Type\""
},
"value": {
"expr": "\"application/json\""
}
},
{
"key": {
"expr": "\"x-jwt-token\""
},
"value": {
"expr": "apiToken"
}
}
],
"body": {
"expr": "{\n \"data\": {\n \"employeeId\": string(employee._id),\n \"contactId\": string(contact._id),\n \"conversationId\": string(conversation._id),\n \"name\": (join(services.filter(s, servicos.exists(id, id == string(s._id))).map(s, s.name), \" + \") != \"\" ? join(services.filter(s, servicos.exists(id, id == string(s._id))).map(s, s.name), \" + \") : \"Agendamento\"),\n \"services\": servicos,\n \"startTimestamp\": horario,\n \"endTimestamp\": add_datetime(horario, (size(services.filter(s, servicos.exists(id, id == string(s._id)))) > 0 ? math_evaluate(join(services.filter(s, servicos.exists(id, id == string(s._id))).map(s, string(get(s, \"durationMinutes\", 0))), \"+\")) : 30), \"minutes\")\n },\n \"userOptions\": {\n \"notifyEmployee\": true,\n \"allowReminderCreation\": true\n }\n}",
"onFailure": "throw",
"strict": true
}
}
}
}
],
"output": {
"main": {
"expr": "(\n step(0).status == 200\n ? (\"Agendamento realizado com sucesso\")\n : (\n step(0).data.error == \"not_available\"\n ? \"Agendamento não realizado, \" +\n employee.name +\n \" não estará disponível \" +\n (step(0).data.reason != null ? step(0).data.reason : \"\")\n : \"Ocorreu um erro ao tentar fazer o agendamento\"\n )\n)",
"onFailure": "throw"
},
"execute_extra_completion": true
}
}
},
{
"type": "function",
"function": {
"name": "consulta_horarios_disponiveis",
"description": "Consulta horários disponíveis num determinado dia. Não leva um consideração a duração de um possível agendamento, dessa forma pode ser que um agendamento não possa ser realizado em um determinado horário dependendo de sua duração.",
"parameters": [
{
"name": "dia",
"type": "string",
"description": "Dia a ser consultado, no formato YYYY-MM-DD, exemplo: 2025-05-30",
"isRequired": true
}
],
"actions": [
{
"condition": {
"expr": "true",
"onFailure": "keep"
},
"action": {
"actionType": "actions/network/http/request/send",
"actions/network/http/request/send": {
"method": "POST",
"url": {
"expr": "apiUrl + \"/dynadata/type/employees/item/\" + employee._id + \"/executeFunction/giveEmployeeSchedule\"",
"onFailure": "keep"
},
"headers": [
{
"key": {
"expr": "\"Content-Type\""
},
"value": {
"expr": "\"application/json\""
}
},
{
"key": {
"expr": "\"x-jwt-token\""
},
"value": {
"expr": "apiToken"
}
}
],
"body": {
"expr": "{\n \"date\": dia,\n \"lang\": \"pt-BR\"\n}",
"onFailure": "keep"
}
}
}
}
],
"output": {
"main": {
"expr": "step(0).status == 200 ? step(0).data.data.text : \"Não foi possível consultar a agenda\"",
"onFailure": "keep"
},
"execute_extra_completion": true
}
}
},
{
"type": "function",
"function": {
"isZodTool": true,
"zodToolSchema": {
"expr": "{\n \"name\": \"finaliza_atendimento\",\n \"description\": \"Finaliza o atendimento automatizado do GPT e notifica o profissional\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"texto_da_notificacao\": {\n \"type\": \"string\",\n \"description\": \"O texto da mensagem que o profissional receberá explicando o motivo da finalização.\"\n }\n },\n \"required\": [\"texto_da_notificacao\"], // Obrigatório seguir este formato\n \"additionalProperties\": false // Recomendado para o modo 'Strict'\n }\n}",
"onFailure": "throw"
},
"actions": [
{
"condition": {
"expr": "true",
"onFailure": "keep"
},
"action": {
"actionType": "actions/assistant/lifecycle/session/pause_replies",
"actions/assistant/lifecycle/session/pause_replies": {
"cooldown_minutes": 30
}
}
},
{
"condition": {
"expr": "true",
"onFailure": "keep"
},
"action": {
"actionType": "actions/notifications/company/notification/create",
"actions/notifications/company/notification/create": {
"title": {
"expr": "contact.name + \" solicita atendimento\"",
"onFailure": "keep"
},
"text": {
"expr": "texto_da_notificacao",
"onFailure": "keep"
},
"buttons": []
}
}
}
],
"output": {
"main": {
"expr": "\"O atendimento foi finalizado o profissional foi notificado\"",
"onFailure": "keep"
}
},
"parameters": []
}
},
{
"type": "function",
"function": {
"name": "busca_agendamentos",
"description": "Busca os ultimos agendamentos do contato",
"parameters": [],
"actions": [
{
"condition": {
"expr": "true",
"onFailure": "keep"
},
"action": {
"actionType": "actions/network/http/request/send",
"actions/network/http/request/send": {
"method": "POST",
"url": {
"expr": "apiUrl + \"/dynadata/type/ct%3Aevents/list\"",
"onFailure": "keep"
},
"headers": [
{
"key": {
"expr": "\"Content-Type\""
},
"value": {
"expr": "\"application/json\""
}
},
{
"key": {
"expr": "\"x-jwt-token\""
},
"value": {
"expr": "userSpace.apiToken"
}
}
],
"body": {
"expr": "{\n \"page\": 1,\n \"pageSize\": 5,\n \"filter\": {\n \"contactId\": {\n \"$eq\": string(contact._id)\n }\n },\n \"sort\": {\n \"startTimestamp\": -1\n }\n}",
"onFailure": "keep"
}
}
}
}
],
"output": {
"main": {
"expr": "step(0).status == 200 ? toon_encode(step(0).data.items) : \"Não foi possível buscar os agendamentos\"",
"onFailure": "throw"
},
"execute_extra_completion": true
}
}
}
]
}
},
"platformType": "openai"
}
}

Bundled custom types (custom_types.json)

Click to expand
[
{
"_id": {
"$oid": "c7a4b1e2d3f4051627384901"
},
"slug": "services",
"name": "Services",
"description": "Service catalogue. Each service defines a duration and price.",
"purpose": "Represents bookable services offered by the company. Referenced from ct:events.services[]. Use when the user asks about pricing, durations, or offerings.",
"fields": [
{
"name": "employeeId",
"description": "Provider employee.",
"schema": {
"type": "ref",
"ref": "employees",
"required": true
},
"cel": {
"label": "t('services_employeeId')"
}
},
{
"name": "name",
"description": "Service name (unique per employee).",
"schema": {
"type": "string",
"required": true,
"trim": true,
"maxLength": 64
},
"cel": {
"label": "t('name')"
}
},
{
"name": "value",
"description": "Price.",
"schema": {
"type": "number",
"required": true,
"min": 0,
"max": 1000
},
"cel": {
"label": "t('price')"
}
},
{
"name": "durationMinutes",
"description": "Duration in minutes.",
"schema": {
"type": "number",
"required": true,
"min": 5,
"max": 360
},
"cel": {
"label": "t('duration')"
}
}
],
"indexedPaths": [
"employeeId"
],
"mcpTips": [
"Every service belongs to a specific employee via employeeId.",
"Event end times are computed from sum of selected service durations (see the agenda-ensure-services-end-timestamp workflow)."
],
"cel": {
"preview": {
"lines": [
{
"parts": [
{
"type": "text",
"expr": "(has(values, 'name') && values.name != '') ? values.name : ffConcat('Service ', ffShortId(values._id))"
}
]
},
{
"parts": [
{
"type": "text",
"expr": "ffConcat(ffMoney(values.value), ' · ', values.durationMinutes, ' min')"
}
]
}
]
}
}
},
{
"_id": {
"$oid": "c7a4b1e2d3f4051627384904"
},
"slug": "employee_periods",
"name": "Employee Periods",
"description": "Time-off and availability overrides for individual employees.",
"purpose": "Represents vacation, sick leave, or custom working hours for a date range. Overrides the employee's normal weekly working_hours.",
"fields": [
{
"name": "employeeId",
"description": "Target employee.",
"schema": {
"type": "ref",
"ref": "employees",
"required": true
},
"cel": {
"label": "t('employee')"
}
},
{
"name": "name",
"description": "Short label.",
"schema": {
"type": "string",
"required": true,
"maxLength": 128
},
"cel": {
"label": "t('name')"
}
},
{
"name": "periodStart",
"description": "Start of the period (inclusive).",
"schema": {
"type": "datetime",
"required": true
},
"cel": {
"label": "t('periodStart')"
}
},
{
"name": "periodEnd",
"description": "End of the period (inclusive).",
"schema": {
"type": "datetime",
"required": true
},
"cel": {
"label": "t('periodEnd')"
}
},
{
"name": "reason",
"description": "Optional free-form reason.",
"schema": {
"type": "string",
"maxLength": 512
},
"cel": {
"label": "t('reason')"
}
},
{
"name": "enabled",
"description": "Whether this override is active.",
"schema": {
"type": "boolean",
"default": true
},
"cel": {
"label": "t('enabled')"
}
},
{
"name": "isDayOff",
"description": "If true, the whole day(s) are blocked.",
"schema": {
"type": "boolean",
"required": true,
"default": false
},
"cel": {
"label": "t('isDayOff')"
}
},
{
"name": "startTime",
"description": "Daily start (HH:mm) when not a day off.",
"schema": {
"type": "string",
"maxLength": 5
},
"cel": {
"label": "t('startTime')",
"hidden": "has(values, 'isDayOff') && values.isDayOff == true"
}
},
{
"name": "endTime",
"description": "Daily end (HH:mm) when not a day off.",
"schema": {
"type": "string",
"maxLength": 5
},
"cel": {
"label": "t('endTime')",
"hidden": "has(values, 'isDayOff') && values.isDayOff == true"
}
}
],
"indexedPaths": [
"employeeId",
"periodStart"
],
"mcpTips": [
"Use this to record vacations and one-off working-hour changes.",
"The agenda-period-overlap-check workflow rejects overlapping periods for the same employee."
],
"calendar": {
"startField": "periodStart",
"endField": "periodEnd",
"titleExpr": "(has(values, 'name') && values.name != '') ? values.name : ffConcat('Period ', ffShortId(values._id))",
"colorExpr": "'#ff6600'",
"allDayExpr": "has(values, 'isDayOff') && values.isDayOff == true",
"groupByField": "employeeId",
"defaultView": "month"
},
"cel": {
"preview": {
"lines": [
{
"parts": [
{
"type": "text",
"expr": "(has(values, 'name') && values.name != '') ? values.name : ffConcat('Period ', ffShortId(values._id))"
}
]
},
{
"parts": [
{
"type": "text",
"expr": "ffConcat(ffFormatDate(values.periodStart, 'YYYY-MM-DD'), ' → ', ffFormatDate(values.periodEnd, 'YYYY-MM-DD'), (has(values, 'isDayOff') && values.isDayOff == true) ? ' · day off' : '')"
}
]
}
]
}
}
},
{
"_id": {
"$oid": "c7a4b1e2d3f4051627384902"
},
"slug": "working_hours",
"name": "Working Hours",
"description": "Per-employee weekly working schedule. One record per (employee, dayOfWeek).",
"purpose": "Defines when each employee is normally available. Used by the calendar to shade non-working slots. Override via ct:employee_periods for specific dates.",
"fields": [
{
"name": "employeeId",
"description": "Target employee.",
"schema": {
"type": "ref",
"ref": "employees",
"required": true
},
"cel": {
"label": "t('employee')"
}
},
{
"name": "dayOfWeek",
"description": "0=Sunday..6=Saturday.",
"schema": {
"type": "number",
"required": true,
"min": 0,
"max": 6
},
"cel": {
"label": "t('dayOfWeek')"
}
},
{
"name": "isDayOff",
"description": "If true, the employee is off this weekday.",
"schema": {
"type": "boolean",
"required": true,
"default": false
},
"cel": {
"label": "t('isDayOff')"
}
},
{
"name": "startTime",
"description": "HH:mm start on working days.",
"schema": {
"type": "string",
"maxLength": 5
},
"cel": {
"label": "t('startTime')",
"hidden": "has(values, 'isDayOff') && values.isDayOff == true"
}
},
{
"name": "endTime",
"description": "HH:mm end on working days.",
"schema": {
"type": "string",
"maxLength": 5
},
"cel": {
"label": "t('endTime')",
"hidden": "has(values, 'isDayOff') && values.isDayOff == true"
}
}
],
"indexedPaths": [
"employeeId",
"dayOfWeek"
],
"mcpTips": [
"Create 7 rows per employee (dayOfWeek 0..6) on onboarding.",
"Per-date overrides live in ct:employee_periods."
],
"cel": {
"preview": {
"lines": [
{
"parts": [
{
"type": "text",
"expr": "(values.dayOfWeek == 0) ? 'Sunday' : (values.dayOfWeek == 1) ? 'Monday' : (values.dayOfWeek == 2) ? 'Tuesday' : (values.dayOfWeek == 3) ? 'Wednesday' : (values.dayOfWeek == 4) ? 'Thursday' : (values.dayOfWeek == 5) ? 'Friday' : (values.dayOfWeek == 6) ? 'Saturday' : ffConcat('Day ', values.dayOfWeek)"
}
]
},
{
"parts": [
{
"type": "text",
"expr": "(has(values, 'isDayOff') && values.isDayOff == true) ? 'Day off' : ffConcat(values.startTime, ' – ', values.endTime)"
}
]
}
]
}
}
},
{
"_id": {
"$oid": "c7a4b1e2d3f4051627384903"
},
"slug": "events",
"name": "Events",
"description": "Calendar events / appointments.",
"purpose": "Represents scheduled appointments between the business and a contact. Supports recurrence via RRULE. Use when the user talks about scheduling, booking, appointments, or calendar entries.",
"fields": [
{
"name": "employeeId",
"description": "Assigned employee.",
"schema": {
"type": "ref",
"ref": "employees",
"required": true
},
"cel": {
"label": "t('employee')"
}
},
{
"name": "contactId",
"description": "Contact the appointment is for.",
"schema": {
"type": "ref",
"ref": "contacts"
},
"cel": {
"label": "t('contact')",
"hidden": "has(values, 'isRecurring') && values.isRecurring == true"
}
},
{
"name": "name",
"description": "Short display name.",
"schema": {
"type": "string",
"required": true,
"trim": true,
"maxLength": 255
},
"cel": {
"label": "t('name')"
}
},
{
"name": "services",
"description": "Services scheduled (refs ct:services).",
"schema": {
"type": "array",
"items": {
"schema": {
"type": "ref",
"ref": "ct:services"
}
},
"default": []
},
"cel": {
"label": "t('services')"
}
},
{
"name": "startTimestamp",
"description": "Event start.",
"schema": {
"type": "datetime",
"required": true
},
"cel": {
"label": "t('startTimestamp')"
}
},
{
"name": "endTimestamp",
"description": "Event end.",
"schema": {
"type": "datetime",
"required": true
},
"cel": {
"label": "t('endTimestamp')"
}
},
{
"name": "isRecurring",
"description": "Whether this event repeats.",
"schema": {
"type": "boolean",
"default": false
},
"cel": {
"label": "t('isRecurring')"
}
},
{
"name": "recurrence",
"description": "RRULE string (when isRecurring).",
"schema": {
"type": "string"
},
"cel": {
"label": "t('recurrence')",
"hidden": "!(has(values, 'isRecurring') && values.isRecurring == true)"
}
},
{
"name": "customAttributes",
"description": "Tenant-defined metadata.",
"schema": {
"type": "json"
},
"cel": {
"label": "t('custom_attributes')"
}
}
],
"indexedPaths": [
"employeeId",
"startTimestamp"
],
"mcpTips": [
"End timestamp is auto-computed from services durations by the agenda-ensure-services-end-timestamp workflow.",
"Name is auto-populated from the contact or first service by the agenda-ensure-name workflow.",
"For recurring events, set isRecurring=true and provide a valid RRULE string."
],
"calendar": {
"startField": "startTimestamp",
"endField": "endTimestamp",
"titleExpr": "has(values, 'name') && values.name != '' ? values.name : ffConcat('Event ', ffShortId(values._id))",
"colorExpr": "has(values, 'isRecurring') && values.isRecurring == true ? 'rgba(0,55,190,0.88)' : '#23a538'",
"recurrenceField": "recurrence",
"groupByField": "employeeId",
"availabilityFromType": "ct:employee_periods",
"workingHoursType": "ct:working_hours",
"defaultView": "month"
},
"cel": {
"preview": {
"lines": [
{
"parts": [
{
"type": "text",
"expr": "(has(values, 'name') && values.name != '') ? values.name : ffConcat('Event ', ffShortId(values._id))"
}
]
},
{
"parts": [
{
"type": "text",
"expr": "ffConcat(ffFormatDate(values.startTimestamp, 'YYYY-MM-DD HH:mm'), (has(values, 'endTimestamp') && values.endTimestamp != null) ? ffConcat(' – ', ffFormatDate(values.endTimestamp, 'HH:mm')) : '', (has(values, 'isRecurring') && values.isRecurring == true) ? ' · recurring' : '')"
}
]
}
]
}
}
}
]

Seed documents (custom_type_docs.json)

Click to expand
[
{
"_id": {
"$oid": "5df3d9175e394707dd181130"
},
"customTypeId": {
"$oid": "c7a4b1e2d3f4051627384901"
},
"data": {
"employeeId": "<employee-id>",
"name": "Corte de cabelo",
"value": 50,
"durationMinutes": 30
}
},
{
"_id": {
"$oid": "dff033ceb254004cbff3c651"
},
"customTypeId": {
"$oid": "c7a4b1e2d3f4051627384901"
},
"data": {
"employeeId": "<employee-id>",
"name": "Barba",
"value": 35,
"durationMinutes": 20
}
},
{
"_id": {
"$oid": "da5bce3930518b821c531b29"
},
"customTypeId": {
"$oid": "c7a4b1e2d3f4051627384901"
},
"data": {
"employeeId": "<employee-id>",
"name": "Corte + Barba",
"value": 75,
"durationMinutes": 45
}
},
{
"_id": {
"$oid": "f1a00296350b9abde4f1fc43"
},
"customTypeId": {
"$oid": "c7a4b1e2d3f4051627384901"
},
"data": {
"employeeId": "<employee-id>",
"name": "Hidratação capilar",
"value": 60,
"durationMinutes": 40
}
},
{
"_id": {
"$oid": "87168436540bd70ff795c35a"
},
"customTypeId": {
"$oid": "c7a4b1e2d3f4051627384901"
},
"data": {
"employeeId": "<employee-id>",
"name": "Sobrancelha",
"value": 25,
"durationMinutes": 15
}
},
{
"_id": {
"$oid": "add4a71e5c9b2f0000000001"
},
"customTypeId": {
"$oid": "c7a4b1e2d3f4051627384902"
},
"data": {
"employeeId": "<employee-id>",
"dayOfWeek": 0,
"isDayOff": true
}
},
{
"_id": {
"$oid": "add4a71e5c9b2f0000000002"
},
"customTypeId": {
"$oid": "c7a4b1e2d3f4051627384902"
},
"data": {
"employeeId": "<employee-id>",
"dayOfWeek": 1,
"isDayOff": false,
"startTime": "09:00",
"endTime": "18:00"
}
},
{
"_id": {
"$oid": "add4a71e5c9b2f0000000003"
},
"customTypeId": {
"$oid": "c7a4b1e2d3f4051627384902"
},
"data": {
"employeeId": "<employee-id>",
"dayOfWeek": 2,
"isDayOff": false,
"startTime": "09:00",
"endTime": "18:00"
}
},
{
"_id": {
"$oid": "add4a71e5c9b2f0000000004"
},
"customTypeId": {
"$oid": "c7a4b1e2d3f4051627384902"
},
"data": {
"employeeId": "<employee-id>",
"dayOfWeek": 3,
"isDayOff": false,
"startTime": "09:00",
"endTime": "18:00"
}
},
{
"_id": {
"$oid": "add4a71e5c9b2f0000000005"
},
"customTypeId": {
"$oid": "c7a4b1e2d3f4051627384902"
},
"data": {
"employeeId": "<employee-id>",
"dayOfWeek": 4,
"isDayOff": false,
"startTime": "09:00",
"endTime": "18:00"
}
},
{
"_id": {
"$oid": "add4a71e5c9b2f0000000006"
},
"customTypeId": {
"$oid": "c7a4b1e2d3f4051627384902"
},
"data": {
"employeeId": "<employee-id>",
"dayOfWeek": 5,
"isDayOff": false,
"startTime": "09:00",
"endTime": "18:00"
}
},
{
"_id": {
"$oid": "add4a71e5c9b2f0000000007"
},
"customTypeId": {
"$oid": "c7a4b1e2d3f4051627384902"
},
"data": {
"employeeId": "<employee-id>",
"dayOfWeek": 6,
"isDayOff": false,
"startTime": "09:00",
"endTime": "13:00"
}
}
]