@fayz-ai/plugin-registry 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +30 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +601 -85
- package/dist/index.js.map +1 -1
- package/dist/lib/entities.d.ts +4 -0
- package/dist/lib/entities.d.ts.map +1 -1
- package/dist/lib/person-fields.d.ts +61 -0
- package/dist/lib/person-fields.d.ts.map +1 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,8 +1,75 @@
|
|
|
1
|
-
import { Briefcase, Tag, MapPin, Map } from 'lucide-react';
|
|
1
|
+
import { Briefcase, Tag, MapPin, Map, UserCog, Users } from 'lucide-react';
|
|
2
2
|
import { createCrudPage, adminNavigateTo } from '@fayz-ai/admin';
|
|
3
|
-
import { tl } from '@fayz-ai/core';
|
|
3
|
+
import { RECORD_GROUP, tl, activeFacet, recordRailFields } from '@fayz-ai/core';
|
|
4
4
|
|
|
5
5
|
// src/index.ts
|
|
6
|
+
var PERSON_GROUPS = [
|
|
7
|
+
// Nome e documento NÃO entram: o arquétipo `person` os hasteia para o
|
|
8
|
+
// cabeçalho da ficha, ao lado da foto. O que sobra é o resto da pessoa.
|
|
9
|
+
{ id: "identity", label: tl("Personal", "Dados pessoais"), columns: 2 },
|
|
10
|
+
{ id: "contact", label: tl("Contact", "Contato"), columns: 2 },
|
|
11
|
+
{ id: "where", label: tl("Address", "Endere\xE7o"), columns: 1 },
|
|
12
|
+
{
|
|
13
|
+
id: "banking",
|
|
14
|
+
label: tl("Banking", "Dados banc\xE1rios"),
|
|
15
|
+
columns: 1,
|
|
16
|
+
description: tl(
|
|
17
|
+
"Where money goes OUT to this person \u2014 not an account of the house.",
|
|
18
|
+
"Para onde o dinheiro SAI para esta pessoa \u2014 n\xE3o \xE9 conta da casa."
|
|
19
|
+
)
|
|
20
|
+
},
|
|
21
|
+
{ ...RECORD_GROUP, label: tl("Record", "Situa\xE7\xE3o") }
|
|
22
|
+
];
|
|
23
|
+
var ACTIVE_FACET = activeFacet({
|
|
24
|
+
active: tl("Active", "Ativos"),
|
|
25
|
+
inactive: tl("Inactive", "Inativos"),
|
|
26
|
+
all: tl("All", "Todos")
|
|
27
|
+
});
|
|
28
|
+
function contactChannelFields() {
|
|
29
|
+
return [
|
|
30
|
+
{ key: "phone", label: tl("Phone", "Telefone"), type: "phone", showInTable: true, searchable: true, group: "contact" },
|
|
31
|
+
{ key: "mobile", label: tl("Mobile", "Celular"), type: "phone", showInTable: true, group: "contact" },
|
|
32
|
+
{ key: "email", label: "E-mail", type: "email", showInTable: true, searchable: true, group: "contact", span: 2 }
|
|
33
|
+
// Desligados — ver acima.
|
|
34
|
+
// { key: 'phone2', label: tl('Phone 2', 'Telefone 2'), type: 'phone', showInTable: false, group: 'contact' },
|
|
35
|
+
// { key: 'mobile2', label: tl('Mobile 2', 'Celular 2'), type: 'phone', showInTable: false, group: 'contact' },
|
|
36
|
+
// { key: 'email2', label: tl('E-mail 2', 'E-mail 2'), type: 'email', showInTable: false, group: 'contact' },
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
function attachedFields() {
|
|
40
|
+
return [
|
|
41
|
+
{ key: "address", label: tl("Address", "Endere\xE7o"), type: "address", showInTable: false, showInDetail: false, group: "where" },
|
|
42
|
+
{ key: "payeeAccount", label: tl("Bank account", "Conta banc\xE1ria"), type: "bank", showInTable: false, showInDetail: false, group: "banking" }
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
function recordFields(table = "people", options = {}) {
|
|
46
|
+
return recordRailFields({
|
|
47
|
+
tagsTable: table,
|
|
48
|
+
activeLabel: tl("Active", "Ativo"),
|
|
49
|
+
// Um cadastro que já pergunta o ESCOPO de unidades não pergunta também a
|
|
50
|
+
// unidade padrão: são duas perguntas parecidas e respostas diferentes, e
|
|
51
|
+
// na tela viravam dois seletores de unidade — um multi à esquerda e um
|
|
52
|
+
// único à direita — sem nada dizendo qual manda.
|
|
53
|
+
unit: options.unit
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function genderField() {
|
|
57
|
+
return {
|
|
58
|
+
key: "gender",
|
|
59
|
+
label: tl("Gender", "Sexo"),
|
|
60
|
+
type: "select",
|
|
61
|
+
showInTable: false,
|
|
62
|
+
group: "identity",
|
|
63
|
+
options: [
|
|
64
|
+
{ value: "masculino", label: tl("Male", "Masculino") },
|
|
65
|
+
{ value: "feminino", label: tl("Female", "Feminino") },
|
|
66
|
+
{ value: "outro", label: tl("Other", "Outro") }
|
|
67
|
+
]
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
var PERSON_CUSTOM_FIELDS = ["gender", "mobile", "phone2", "mobile2", "email2"];
|
|
71
|
+
|
|
72
|
+
// src/lib/entities.ts
|
|
6
73
|
var staffEntity = {
|
|
7
74
|
name: tl("Staff", "Profissional"),
|
|
8
75
|
namePlural: tl("Staff", "Profissionais"),
|
|
@@ -13,16 +80,80 @@ var staffEntity = {
|
|
|
13
80
|
agentHidden: true,
|
|
14
81
|
layout: "person",
|
|
15
82
|
displayField: "name",
|
|
83
|
+
subtitleField: "profession",
|
|
16
84
|
defaultSort: "name",
|
|
85
|
+
formColumns: 2,
|
|
86
|
+
facets: [ACTIVE_FACET],
|
|
17
87
|
fieldGroups: [
|
|
18
|
-
|
|
88
|
+
...PERSON_GROUPS.filter((g) => g.id !== "record"),
|
|
89
|
+
{
|
|
90
|
+
id: "work",
|
|
91
|
+
label: tl("Work", "Trabalho"),
|
|
92
|
+
columns: 2,
|
|
93
|
+
description: tl(
|
|
94
|
+
"What this person does here, and how the calendar and the payroll see them.",
|
|
95
|
+
"O que esta pessoa faz aqui, e como a agenda e a folha a enxergam."
|
|
96
|
+
)
|
|
97
|
+
},
|
|
98
|
+
{ ...RECORD_GROUP }
|
|
19
99
|
],
|
|
20
100
|
fields: [
|
|
21
|
-
{ key: "name", label: tl("
|
|
22
|
-
{ key: "
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
{ key: "
|
|
101
|
+
{ key: "name", label: tl("Full name", "Nome completo"), type: "text", required: true, showInTable: true, searchable: true, group: "identity", span: 2 },
|
|
102
|
+
{ key: "documentNumber", label: "CPF", type: "text", showInTable: true, searchable: true, group: "identity" },
|
|
103
|
+
// RG: o V1 pede os dois documentos na ficha de quem trabalha aqui, e a
|
|
104
|
+
// folha usa ambos.
|
|
105
|
+
{ key: "rg", label: "RG", type: "text", showInTable: false, group: "identity" },
|
|
106
|
+
{ key: "dateOfBirth", label: tl("Date of birth", "Data de nascimento"), type: "date", showInTable: false, group: "identity" },
|
|
107
|
+
genderField(),
|
|
108
|
+
...contactChannelFields(),
|
|
109
|
+
...attachedFields(),
|
|
110
|
+
{ key: "profession", label: tl("Profession", "Profiss\xE3o"), type: "text", showInTable: true, group: "work" },
|
|
111
|
+
// O VÍNCULO muda o que a folha e o fisco esperam — um autônomo emite RPA,
|
|
112
|
+
// um CLT entra na folha, um PJ manda nota.
|
|
113
|
+
{
|
|
114
|
+
key: "employmentType",
|
|
115
|
+
label: tl("Employment type", "Tipo de v\xEDnculo"),
|
|
116
|
+
type: "select",
|
|
117
|
+
showInTable: false,
|
|
118
|
+
group: "work",
|
|
119
|
+
options: [
|
|
120
|
+
{ value: "clt", label: "CLT" },
|
|
121
|
+
{ value: "pj", label: "Pessoa jur\xEDdica" },
|
|
122
|
+
{ value: "autonomo", label: "Aut\xF4nomo" },
|
|
123
|
+
{ value: "estagio", label: "Est\xE1gio" },
|
|
124
|
+
{ value: "socio", label: "S\xF3cio" }
|
|
125
|
+
]
|
|
126
|
+
},
|
|
127
|
+
// CBO é o código que a folha e o eSocial pedem. Texto porque a tabela
|
|
128
|
+
// oficial tem ~2.500 entradas e um seletor delas seria pior que o campo.
|
|
129
|
+
{
|
|
130
|
+
key: "cboCode",
|
|
131
|
+
label: tl("CBO code", "CBO"),
|
|
132
|
+
type: "text",
|
|
133
|
+
showInTable: false,
|
|
134
|
+
group: "work",
|
|
135
|
+
hint: tl("Brazilian occupation code, used by payroll.", "Classifica\xE7\xE3o Brasileira de Ocupa\xE7\xF5es \u2014 a folha pede.")
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
key: "teamId",
|
|
139
|
+
label: tl("Team", "Equipe"),
|
|
140
|
+
type: "relation",
|
|
141
|
+
showInTable: true,
|
|
142
|
+
group: "work",
|
|
143
|
+
relation: { table: "categories", filter: { kind: "staff_team", is_active: true } },
|
|
144
|
+
hint: tl("Where this person works \u2014 reception, aesthetics, sales.", "Onde esta pessoa trabalha \u2014 recep\xE7\xE3o, est\xE9tica, vendas.")
|
|
145
|
+
},
|
|
146
|
+
{ key: "commissionRate", label: tl("Commission (%)", "Comiss\xE3o (%)"), type: "number", showInTable: true, group: "work", defaultValue: 0 },
|
|
147
|
+
// O CENTRO DE CUSTO da pessoa é o que permite alocar o custo dela por
|
|
148
|
+
// área — é assim que a folha vira despesa de Operação e não um bolo só.
|
|
149
|
+
{
|
|
150
|
+
key: "costCenterId",
|
|
151
|
+
label: tl("Cost centre", "Centro de custo"),
|
|
152
|
+
type: "relation",
|
|
153
|
+
showInTable: false,
|
|
154
|
+
group: "work",
|
|
155
|
+
relation: { table: "plg_financial_cost_centers", labelField: "name" }
|
|
156
|
+
},
|
|
26
157
|
// Trabalhar aqui não é atender cliente: a recepção e o financeiro entram
|
|
27
158
|
// por esta mesma tela. Sem a escolha no cadastro, todo mundo nascia com
|
|
28
159
|
// coluna na grade da semana e horário livre para marcarem em cima.
|
|
@@ -32,20 +163,20 @@ var staffEntity = {
|
|
|
32
163
|
type: "boolean",
|
|
33
164
|
showInTable: true,
|
|
34
165
|
defaultValue: true,
|
|
35
|
-
|
|
36
|
-
group: "professional",
|
|
166
|
+
group: "work",
|
|
37
167
|
hint: tl(
|
|
38
168
|
"Clients can be booked with this person.",
|
|
39
169
|
"Clientes podem ser marcados com esta pessoa."
|
|
40
170
|
)
|
|
41
171
|
},
|
|
42
|
-
|
|
172
|
+
// O que a recepção precisa saber ANTES de marcar com esta pessoa.
|
|
173
|
+
{ key: "agendaNotes", label: tl("Calendar notes", "Informa\xE7\xF5es para a agenda"), type: "textarea", showInTable: false, group: "work", span: 2 },
|
|
174
|
+
...recordFields()
|
|
43
175
|
],
|
|
44
176
|
// No custom documents tab: the `person` archetype supplies the shared
|
|
45
177
|
// custom_forms documents widget (same as clients / school-saas).
|
|
46
178
|
data: {
|
|
47
179
|
// Pessoa pura, como no resto-saas: quem é da equipe é `people.kind='staff'`.
|
|
48
|
-
// A extensão `staff_members` e a view `v_staff` não existem mais.
|
|
49
180
|
table: "people",
|
|
50
181
|
tenantScoped: true,
|
|
51
182
|
archetype: "person",
|
|
@@ -54,8 +185,18 @@ var staffEntity = {
|
|
|
54
185
|
// verdade é a do motor de regras do Financeiro (plg_financial_commission_*),
|
|
55
186
|
// que sabe dividir por serviço e por profissional; este número aqui é o
|
|
56
187
|
// resquício de quando havia um só. Fica em custom_fields para não sumir.
|
|
57
|
-
customFields: [
|
|
58
|
-
|
|
188
|
+
customFields: [
|
|
189
|
+
...PERSON_CUSTOM_FIELDS,
|
|
190
|
+
"profession",
|
|
191
|
+
"commissionRate",
|
|
192
|
+
"teamId",
|
|
193
|
+
"employmentType",
|
|
194
|
+
"cboCode",
|
|
195
|
+
"agendaNotes",
|
|
196
|
+
"rg",
|
|
197
|
+
"costCenterId"
|
|
198
|
+
],
|
|
199
|
+
searchColumns: ["name", "email", "phone", "document_number"]
|
|
59
200
|
}
|
|
60
201
|
};
|
|
61
202
|
var supplierEntity = {
|
|
@@ -64,15 +205,15 @@ var supplierEntity = {
|
|
|
64
205
|
icon: "Building2",
|
|
65
206
|
layout: "person",
|
|
66
207
|
displayField: "name",
|
|
208
|
+
subtitleField: "documentNumber",
|
|
67
209
|
defaultSort: "name",
|
|
68
210
|
formColumns: 2,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
{ id: "contact", label: tl("Contact", "Contato"), columns: 2 },
|
|
72
|
-
{ id: "record", label: tl("Record", "Cadastro"), columns: 1, column: "side" }
|
|
73
|
-
],
|
|
211
|
+
facets: [ACTIVE_FACET],
|
|
212
|
+
fieldGroups: PERSON_GROUPS,
|
|
74
213
|
fields: [
|
|
75
214
|
{ key: "name", label: tl("Legal name / Name", "Raz\xE3o social / Nome"), type: "text", required: true, showInTable: true, searchable: true, group: "identity", span: 2 },
|
|
215
|
+
// O tipo de prestador MUDA o documento: um autônomo assina com CPF, uma
|
|
216
|
+
// empresa com CNPJ, e a inscrição estadual só existe para quem é inscrito.
|
|
76
217
|
{
|
|
77
218
|
key: "serviceProviderType",
|
|
78
219
|
label: tl("Service provider type", "Tipo de prestador de servi\xE7o"),
|
|
@@ -87,22 +228,42 @@ var supplierEntity = {
|
|
|
87
228
|
{ value: "autonomo", label: tl("Self-employed", "Aut\xF4nomo") }
|
|
88
229
|
]
|
|
89
230
|
},
|
|
90
|
-
{
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
231
|
+
{
|
|
232
|
+
key: "documentNumber",
|
|
233
|
+
type: "text",
|
|
234
|
+
searchable: true,
|
|
235
|
+
showInTable: true,
|
|
236
|
+
group: "identity",
|
|
237
|
+
label: "CNPJ / CPF",
|
|
238
|
+
// Rotular sempre «CNPJ / CPF» faz o operador parar para escolher qual é.
|
|
239
|
+
// Ele já escolheu na linha de cima.
|
|
240
|
+
labelWhen: (v) => v.serviceProviderType === "pj" ? "CNPJ" : "CPF"
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
key: "registrationNumber",
|
|
244
|
+
label: tl("State / municipal registration", "Inscri\xE7\xE3o estadual / municipal"),
|
|
245
|
+
type: "text",
|
|
246
|
+
group: "identity",
|
|
247
|
+
// Pessoa física não tem inscrição estadual. Perguntar é convidar a
|
|
248
|
+
// preencher o que ninguém vai ler.
|
|
249
|
+
visibleWhen: (v) => v.serviceProviderType !== "pf"
|
|
250
|
+
},
|
|
251
|
+
// O GRUPO do V1 («Grupo 1», «Grupo 2»): como a casa separa os fornecedores
|
|
252
|
+
// dela. Texto livre porque no V1 é isso mesmo — uma taxonomia por conta
|
|
253
|
+
// viraria um cadastro que ninguém pediu.
|
|
254
|
+
{ key: "groupName", label: tl("Group", "Grupo"), type: "text", showInTable: false, group: "identity" },
|
|
255
|
+
...contactChannelFields(),
|
|
256
|
+
...attachedFields(),
|
|
257
|
+
...recordFields()
|
|
96
258
|
],
|
|
97
259
|
data: {
|
|
98
260
|
table: "people",
|
|
99
|
-
schema: "public",
|
|
100
261
|
tenantScoped: true,
|
|
101
262
|
archetype: "person",
|
|
102
263
|
archetypeKind: "supplier",
|
|
103
264
|
filters: { kind: "supplier" },
|
|
104
265
|
defaults: { kind: "supplier" },
|
|
105
|
-
customFields: ["serviceProviderType", "registrationNumber"],
|
|
266
|
+
customFields: [...PERSON_CUSTOM_FIELDS, "serviceProviderType", "registrationNumber", "groupName"],
|
|
106
267
|
searchColumns: ["name", "email", "phone", "document_number"]
|
|
107
268
|
}
|
|
108
269
|
};
|
|
@@ -112,23 +273,31 @@ var contactEntity = {
|
|
|
112
273
|
icon: "Contact",
|
|
113
274
|
layout: "person",
|
|
114
275
|
displayField: "name",
|
|
276
|
+
subtitleField: "documentNumber",
|
|
115
277
|
defaultSort: "name",
|
|
278
|
+
formColumns: 2,
|
|
279
|
+
facets: [ACTIVE_FACET],
|
|
280
|
+
fieldGroups: PERSON_GROUPS,
|
|
116
281
|
fields: [
|
|
117
|
-
{ key: "name", label: tl("Name", "Nome"), type: "text", required: true, showInTable: true, searchable: true },
|
|
118
|
-
{ key: "
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
282
|
+
{ key: "name", label: tl("Name", "Nome"), type: "text", required: true, showInTable: true, searchable: true, group: "identity", span: 2 },
|
|
283
|
+
{ key: "documentNumber", label: "CPF / CNPJ", type: "text", showInTable: true, searchable: true, group: "identity" },
|
|
284
|
+
genderField(),
|
|
285
|
+
...contactChannelFields(),
|
|
286
|
+
...attachedFields(),
|
|
287
|
+
...recordFields()
|
|
122
288
|
],
|
|
123
289
|
data: {
|
|
124
290
|
table: "people",
|
|
125
|
-
schema:
|
|
291
|
+
// Sem `schema`, de propósito: com ele a entidade resolvia para o provider
|
|
292
|
+
// genérico de tabela, que não sabe ler nem gravar `custom_fields` — e
|
|
293
|
+
// metade desta ficha mora lá. `public` já é o padrão.
|
|
126
294
|
tenantScoped: true,
|
|
127
295
|
archetype: "person",
|
|
128
296
|
archetypeKind: "contact",
|
|
129
297
|
filters: { kind: "contact" },
|
|
130
298
|
defaults: { kind: "contact" },
|
|
131
|
-
|
|
299
|
+
customFields: PERSON_CUSTOM_FIELDS,
|
|
300
|
+
searchColumns: ["name", "email", "phone", "document_number"]
|
|
132
301
|
}
|
|
133
302
|
};
|
|
134
303
|
var partnershipEntity = {
|
|
@@ -138,19 +307,50 @@ var partnershipEntity = {
|
|
|
138
307
|
layout: "person",
|
|
139
308
|
displayField: "name",
|
|
140
309
|
defaultSort: "name",
|
|
310
|
+
formColumns: 2,
|
|
311
|
+
facets: [ACTIVE_FACET],
|
|
312
|
+
fieldGroups: PERSON_GROUPS,
|
|
141
313
|
fields: [
|
|
142
|
-
{ key: "name", label: tl("
|
|
143
|
-
{ key: "
|
|
144
|
-
|
|
314
|
+
{ key: "name", label: tl("Partnership name", "Nome da parceria"), type: "text", required: true, showInTable: true, searchable: true, group: "identity", span: 2 },
|
|
315
|
+
{ key: "documentNumber", label: "CNPJ / CPF", type: "text", showInTable: true, searchable: true, group: "identity" },
|
|
316
|
+
// O ESCOPO de unidades, que no V1 é o par «Limitar unidades? → quais».
|
|
317
|
+
// Escolher «Todas» e ainda ver a lista de unidades é oferecer uma resposta
|
|
318
|
+
// que não vale; por isso a lista só aparece depois da escolha.
|
|
319
|
+
{
|
|
320
|
+
key: "unitScope",
|
|
321
|
+
label: tl("Units", "Unidades"),
|
|
322
|
+
type: "segmented",
|
|
323
|
+
defaultValue: "all",
|
|
324
|
+
group: "identity",
|
|
325
|
+
span: 2,
|
|
326
|
+
options: [
|
|
327
|
+
{ value: "all", label: tl("All units", "Todas as unidades") },
|
|
328
|
+
{ value: "selected", label: tl("Pick units", "Selecionar unidades") }
|
|
329
|
+
]
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
key: "units",
|
|
333
|
+
label: tl("Selected units", "Unidades selecionadas"),
|
|
334
|
+
type: "relation",
|
|
335
|
+
showInTable: false,
|
|
336
|
+
group: "identity",
|
|
337
|
+
span: 2,
|
|
338
|
+
relation: { table: "v_units", labelField: "name" },
|
|
339
|
+
visibleWhen: (v) => v.unitScope === "selected"
|
|
340
|
+
},
|
|
341
|
+
...contactChannelFields(),
|
|
342
|
+
...attachedFields(),
|
|
343
|
+
...recordFields("people", { unit: false })
|
|
145
344
|
],
|
|
146
345
|
data: {
|
|
147
346
|
table: "people",
|
|
148
|
-
schema: "public",
|
|
149
347
|
tenantScoped: true,
|
|
150
348
|
archetype: "person",
|
|
151
349
|
archetypeKind: "partner",
|
|
152
350
|
filters: { kind: "partner" },
|
|
153
|
-
defaults: { kind: "partner" }
|
|
351
|
+
defaults: { kind: "partner" },
|
|
352
|
+
customFields: [...PERSON_CUSTOM_FIELDS, "unitScope", "units"],
|
|
353
|
+
searchColumns: ["name", "email", "phone", "document_number"]
|
|
154
354
|
}
|
|
155
355
|
};
|
|
156
356
|
var equipmentEntity = {
|
|
@@ -160,18 +360,69 @@ var equipmentEntity = {
|
|
|
160
360
|
layout: "product",
|
|
161
361
|
displayField: "name",
|
|
162
362
|
defaultSort: "name",
|
|
363
|
+
formColumns: 2,
|
|
364
|
+
facets: [ACTIVE_FACET],
|
|
365
|
+
fieldGroups: [
|
|
366
|
+
{ id: "identity", label: tl("Identification", "Identifica\xE7\xE3o"), columns: 2 },
|
|
367
|
+
{ id: "purchase", label: tl("Purchase", "Compra e garantia"), columns: 2 },
|
|
368
|
+
{
|
|
369
|
+
id: "maintenance",
|
|
370
|
+
label: tl("Maintenance", "Manuten\xE7\xE3o"),
|
|
371
|
+
columns: 2,
|
|
372
|
+
description: tl(
|
|
373
|
+
"The next date is what a list of equipment is FOR.",
|
|
374
|
+
"A pr\xF3xima data \xE9 para o que uma lista de equipamentos serve."
|
|
375
|
+
)
|
|
376
|
+
},
|
|
377
|
+
{ ...RECORD_GROUP }
|
|
378
|
+
],
|
|
163
379
|
fields: [
|
|
164
|
-
{ key: "name", label: tl("Name", "Nome"), type: "text", required: true, showInTable: true, searchable: true },
|
|
165
|
-
{ key: "
|
|
166
|
-
{ key: "
|
|
167
|
-
|
|
380
|
+
{ key: "name", label: tl("Name", "Nome"), type: "text", required: true, showInTable: true, searchable: true, group: "identity", span: 2 },
|
|
381
|
+
{ key: "brand", label: tl("Brand", "Marca"), type: "text", showInTable: true, group: "identity" },
|
|
382
|
+
{ key: "model", label: tl("Model", "Modelo"), type: "text", showInTable: true, group: "identity" },
|
|
383
|
+
// O NÚMERO DE SÉRIE é o que a assistência técnica pede e o que distingue
|
|
384
|
+
// dois aparelhos iguais na mesma sala.
|
|
385
|
+
{ key: "sku", label: tl("Serial number", "N\xFAmero de s\xE9rie"), type: "text", showInTable: false, searchable: true, group: "identity" },
|
|
386
|
+
{
|
|
387
|
+
key: "status",
|
|
388
|
+
label: "Status",
|
|
389
|
+
type: "select",
|
|
390
|
+
showInTable: true,
|
|
391
|
+
defaultValue: "active",
|
|
392
|
+
group: "identity",
|
|
393
|
+
options: [
|
|
394
|
+
{ value: "active", label: tl("Active", "Ativo") },
|
|
395
|
+
{ value: "maintenance", label: tl("In maintenance", "Em manuten\xE7\xE3o") },
|
|
396
|
+
{ value: "inactive", label: tl("Retired", "Inativo") }
|
|
397
|
+
]
|
|
398
|
+
},
|
|
399
|
+
// A SALA onde ele está. Aponta para o cadastro de Locais.
|
|
400
|
+
{
|
|
401
|
+
key: "serviceLocationId",
|
|
402
|
+
label: tl("Location", "Local"),
|
|
403
|
+
type: "relation",
|
|
404
|
+
showInTable: true,
|
|
405
|
+
group: "identity",
|
|
406
|
+
relation: { table: "v_units", labelField: "name" }
|
|
407
|
+
},
|
|
408
|
+
{ key: "purchaseDate", label: tl("Purchase date", "Data de compra"), type: "date", showInTable: false, group: "purchase" },
|
|
409
|
+
{ key: "purchaseValue", label: tl("Purchase value", "Valor de compra"), type: "currency", showInTable: false, group: "purchase" },
|
|
410
|
+
{ key: "warrantyExpiration", label: tl("Warranty ends", "Vencimento da garantia"), type: "date", showInTable: false, group: "purchase" },
|
|
411
|
+
{ key: "lastMaintenanceDate", label: tl("Last maintenance", "\xDAltima manuten\xE7\xE3o"), type: "date", showInTable: false, group: "maintenance" },
|
|
412
|
+
// A COLUNA que justifica a lista: quem abre «Equipamentos» está quase
|
|
413
|
+
// sempre perguntando o que vence agora.
|
|
414
|
+
{ key: "nextMaintenanceDate", label: tl("Next maintenance", "Pr\xF3xima manuten\xE7\xE3o"), type: "date", showInTable: true, group: "maintenance" },
|
|
415
|
+
{ key: "maintenanceNotes", label: tl("Maintenance notes", "Notas de manuten\xE7\xE3o"), type: "textarea", showInTable: false, group: "maintenance", span: 2 },
|
|
416
|
+
{ key: "description", label: tl("Description", "Descri\xE7\xE3o"), type: "textarea", showInTable: false, group: "identity", span: 2 },
|
|
417
|
+
...recordRailFields({ tagsTable: "products", activeLabel: tl("Active", "Ativo") })
|
|
168
418
|
],
|
|
169
419
|
data: {
|
|
170
420
|
table: "products",
|
|
171
421
|
schema: "public",
|
|
172
422
|
tenantScoped: true,
|
|
173
423
|
filters: { kind: "asset" },
|
|
174
|
-
defaults: { kind: "asset" }
|
|
424
|
+
defaults: { kind: "asset" },
|
|
425
|
+
searchColumns: ["name", "sku"]
|
|
175
426
|
}
|
|
176
427
|
};
|
|
177
428
|
var originEntity = {
|
|
@@ -180,16 +431,68 @@ var originEntity = {
|
|
|
180
431
|
icon: "Globe",
|
|
181
432
|
displayField: "name",
|
|
182
433
|
defaultSort: "name",
|
|
434
|
+
formColumns: 2,
|
|
435
|
+
facets: [ACTIVE_FACET],
|
|
436
|
+
fieldGroups: [
|
|
437
|
+
{ id: "identity", label: tl("Identification", "Identifica\xE7\xE3o"), columns: 1 },
|
|
438
|
+
{ ...RECORD_GROUP }
|
|
439
|
+
],
|
|
183
440
|
fields: [
|
|
184
|
-
{ key: "name", label: tl("Name", "Nome"), type: "text", required: true, showInTable: true, searchable: true },
|
|
185
|
-
|
|
441
|
+
{ key: "name", label: tl("Name", "Nome"), type: "text", required: true, showInTable: true, searchable: true, group: "identity" },
|
|
442
|
+
// «SOLICITAR PESSOA» é uma regra que vale do OUTRO lado: ligada, escolher
|
|
443
|
+
// esta origem no cadastro de cliente passa a exigir quem indicou. Sem ela,
|
|
444
|
+
// «indicação de amigo» vira uma estatística sem nome nenhum atrás.
|
|
445
|
+
{
|
|
446
|
+
key: "requiresPerson",
|
|
447
|
+
label: tl("Ask who referred", "Solicitar pessoa"),
|
|
448
|
+
type: "boolean",
|
|
449
|
+
showInTable: true,
|
|
450
|
+
defaultValue: false,
|
|
451
|
+
group: "identity",
|
|
452
|
+
hint: tl(
|
|
453
|
+
"Choosing this origin on a client will require naming who referred them.",
|
|
454
|
+
"Escolher esta origem num cliente passa a exigir dizer quem indicou."
|
|
455
|
+
)
|
|
456
|
+
},
|
|
457
|
+
// A DESCRIÇÃO da origem é a observação: `categories` não tem coluna
|
|
458
|
+
// separada, e duas colunas para uma frase é invenção. Ela vai para a
|
|
459
|
+
// lista porque é o que distingue «Indicação» de «Indicação de parceiro».
|
|
460
|
+
...recordRailFields({ tagsTable: "categories", activeLabel: tl("Active", "Ativo"), unit: false, notesInTable: true })
|
|
186
461
|
],
|
|
187
462
|
data: {
|
|
188
463
|
table: "categories",
|
|
189
464
|
schema: "public",
|
|
190
465
|
tenantScoped: true,
|
|
191
466
|
filters: { kind: "origin" },
|
|
192
|
-
defaults: { kind: "origin" }
|
|
467
|
+
defaults: { kind: "origin" },
|
|
468
|
+
customFields: ["requiresPerson"],
|
|
469
|
+
searchColumns: ["name"]
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
var teamEntity = {
|
|
473
|
+
name: tl("Team", "Equipe"),
|
|
474
|
+
namePlural: tl("Teams", "Equipes"),
|
|
475
|
+
icon: "Users",
|
|
476
|
+
displayField: "name",
|
|
477
|
+
formColumns: 2,
|
|
478
|
+
facets: [ACTIVE_FACET],
|
|
479
|
+
fieldGroups: [{ ...RECORD_GROUP }],
|
|
480
|
+
defaultSort: "sortOrder",
|
|
481
|
+
fields: [
|
|
482
|
+
{ key: "name", label: tl("Name", "Nome"), type: "text", required: true, showInTable: true, searchable: true },
|
|
483
|
+
// A cor é o que faz a equipe se reconhecer de relance na grade da semana e
|
|
484
|
+
// no quadro — é o mesmo papel que ela tem na etiqueta.
|
|
485
|
+
{ key: "color", label: tl("Colour", "Cor"), type: "color", showInTable: true },
|
|
486
|
+
{ key: "sortOrder", label: tl("Order", "Ordem"), type: "number", showInTable: true, defaultValue: 0 },
|
|
487
|
+
...recordRailFields({ tagsTable: "categories", activeLabel: tl("Active", "Ativo"), unit: false })
|
|
488
|
+
],
|
|
489
|
+
data: {
|
|
490
|
+
table: "categories",
|
|
491
|
+
schema: "public",
|
|
492
|
+
tenantScoped: true,
|
|
493
|
+
filters: { kind: "staff_team" },
|
|
494
|
+
defaults: { kind: "staff_team" },
|
|
495
|
+
searchColumns: ["name"]
|
|
193
496
|
}
|
|
194
497
|
};
|
|
195
498
|
var serviceCategoryEntity = {
|
|
@@ -197,11 +500,14 @@ var serviceCategoryEntity = {
|
|
|
197
500
|
namePlural: tl("Service Categories", "Categorias de Servi\xE7o"),
|
|
198
501
|
icon: "Tag",
|
|
199
502
|
displayField: "name",
|
|
503
|
+
formColumns: 2,
|
|
504
|
+
facets: [ACTIVE_FACET],
|
|
505
|
+
fieldGroups: [{ ...RECORD_GROUP }],
|
|
200
506
|
defaultSort: "sortOrder",
|
|
201
507
|
fields: [
|
|
202
508
|
{ key: "name", label: tl("Name", "Nome"), type: "text", required: true, showInTable: true, searchable: true },
|
|
203
509
|
{ key: "sortOrder", label: tl("Order", "Ordem"), type: "number", showInTable: true, defaultValue: 0 },
|
|
204
|
-
{
|
|
510
|
+
...recordRailFields({ tagsTable: "categories", activeLabel: tl("Active", "Ativo"), unit: false })
|
|
205
511
|
],
|
|
206
512
|
data: {
|
|
207
513
|
table: "categories",
|
|
@@ -212,35 +518,57 @@ var serviceCategoryEntity = {
|
|
|
212
518
|
}
|
|
213
519
|
};
|
|
214
520
|
var serviceLocationEntity = {
|
|
215
|
-
name: tl("
|
|
216
|
-
namePlural: tl("
|
|
521
|
+
name: tl("Location", "Local"),
|
|
522
|
+
namePlural: tl("Locations", "Locais"),
|
|
217
523
|
icon: "MapPin",
|
|
218
|
-
layout: "location",
|
|
219
524
|
displayField: "name",
|
|
220
|
-
subtitleField: "city",
|
|
221
525
|
defaultSort: "name",
|
|
526
|
+
formColumns: 2,
|
|
527
|
+
facets: [ACTIVE_FACET],
|
|
528
|
+
fieldGroups: [
|
|
529
|
+
{
|
|
530
|
+
id: "identity",
|
|
531
|
+
label: tl("The location", "O local"),
|
|
532
|
+
columns: 2,
|
|
533
|
+
description: tl(
|
|
534
|
+
"Where you attend, inside a unit. Not a unit: no till, no stock, no permission of its own.",
|
|
535
|
+
"Onde se atende, dentro de uma unidade. N\xE3o \xE9 unidade: n\xE3o tem caixa, n\xE3o tem estoque e n\xE3o recorta permiss\xE3o."
|
|
536
|
+
)
|
|
537
|
+
},
|
|
538
|
+
{ ...RECORD_GROUP }
|
|
539
|
+
],
|
|
222
540
|
fields: [
|
|
223
|
-
{ key: "name", label: tl("Name", "Nome"), type: "text", required: true, showInTable: true, searchable: true },
|
|
224
|
-
{ key: "
|
|
225
|
-
{
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
541
|
+
{ key: "name", label: tl("Name", "Nome"), type: "text", required: true, showInTable: true, searchable: true, group: "identity" },
|
|
542
|
+
{ key: "code", label: tl("Code", "C\xF3digo"), type: "text", showInTable: true, searchable: true, group: "identity" },
|
|
543
|
+
{
|
|
544
|
+
key: "groupId",
|
|
545
|
+
label: tl("Group", "Grupo"),
|
|
546
|
+
type: "relation",
|
|
547
|
+
showInTable: true,
|
|
548
|
+
group: "identity",
|
|
549
|
+
relation: { table: "categories", labelField: "name", filter: { kind: "location_group" } }
|
|
550
|
+
},
|
|
551
|
+
// A LOTAÇÃO é o que impede a agenda de marcar cinco pessoas às 14h onde
|
|
552
|
+
// cabem quatro. Sem ela o local é só um rótulo.
|
|
553
|
+
{
|
|
554
|
+
key: "capacity",
|
|
555
|
+
label: tl("Capacity", "M\xE1ximo de clientes simult\xE2neos"),
|
|
556
|
+
type: "number",
|
|
557
|
+
min: 1,
|
|
558
|
+
defaultValue: 1,
|
|
559
|
+
showInTable: true,
|
|
560
|
+
group: "identity",
|
|
561
|
+
hint: tl("One chair is 1. A tanning room with four beds is 4.", "Uma cadeira \xE9 1. Uma sala de bronzeamento com quatro macas \xE9 4.")
|
|
562
|
+
},
|
|
563
|
+
// A unidade vem do RAIL, com todos os outros «de quem é este registro» —
|
|
564
|
+
// e obrigatória, porque um local sem prédio não existe.
|
|
565
|
+
...recordRailFields({ tagsTable: "rooms", activeLabel: tl("Active", "Ativo"), unitRequired: true })
|
|
234
566
|
],
|
|
235
567
|
data: {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
// ativo, observações). A tabela `locations` não existe mais.
|
|
239
|
-
table: "v_units",
|
|
568
|
+
table: "rooms",
|
|
569
|
+
schema: "public",
|
|
240
570
|
tenantScoped: true,
|
|
241
|
-
|
|
242
|
-
archetypeKind: "branch",
|
|
243
|
-
searchColumns: ["name", "city", "state"]
|
|
571
|
+
searchColumns: ["name", "code"]
|
|
244
572
|
}
|
|
245
573
|
};
|
|
246
574
|
var locationGroupEntity = {
|
|
@@ -248,11 +576,14 @@ var locationGroupEntity = {
|
|
|
248
576
|
namePlural: tl("Location Groups", "Grupos de Locais"),
|
|
249
577
|
icon: "Map",
|
|
250
578
|
displayField: "name",
|
|
579
|
+
formColumns: 2,
|
|
580
|
+
facets: [ACTIVE_FACET],
|
|
581
|
+
fieldGroups: [{ ...RECORD_GROUP }],
|
|
251
582
|
defaultSort: "sortOrder",
|
|
252
583
|
fields: [
|
|
253
584
|
{ key: "name", label: tl("Name", "Nome"), type: "text", required: true, showInTable: true, searchable: true },
|
|
254
585
|
{ key: "sortOrder", label: tl("Order", "Ordem"), type: "number", showInTable: true, defaultValue: 0 },
|
|
255
|
-
{
|
|
586
|
+
...recordRailFields({ tagsTable: "categories", activeLabel: tl("Active", "Ativo"), unit: false })
|
|
256
587
|
],
|
|
257
588
|
data: {
|
|
258
589
|
table: "categories",
|
|
@@ -265,32 +596,65 @@ var locationGroupEntity = {
|
|
|
265
596
|
};
|
|
266
597
|
var serviceEntity = {
|
|
267
598
|
name: tl("Service", "Servi\xE7o"),
|
|
599
|
+
namePlural: tl("Services", "Servi\xE7os"),
|
|
268
600
|
icon: "Briefcase",
|
|
269
601
|
layout: "service",
|
|
270
602
|
displayField: "name",
|
|
271
603
|
defaultSort: "name",
|
|
604
|
+
formColumns: 2,
|
|
605
|
+
facets: [ACTIVE_FACET],
|
|
606
|
+
fieldGroups: [
|
|
607
|
+
{ id: "identity", label: tl("Identification", "Identifica\xE7\xE3o"), columns: 2 },
|
|
608
|
+
{ id: "pricing", label: tl("Price and time", "Pre\xE7o e tempo"), columns: 2 },
|
|
609
|
+
{
|
|
610
|
+
id: "texts",
|
|
611
|
+
label: tl("What the client reads", "O que o cliente l\xEA"),
|
|
612
|
+
columns: 1,
|
|
613
|
+
description: tl(
|
|
614
|
+
"Written once here, sent every time the service is booked or finished.",
|
|
615
|
+
"Escrito uma vez aqui, enviado toda vez que o servi\xE7o \xE9 marcado ou conclu\xEDdo."
|
|
616
|
+
)
|
|
617
|
+
},
|
|
618
|
+
{ ...RECORD_GROUP }
|
|
619
|
+
],
|
|
272
620
|
fields: [
|
|
273
|
-
{ key: "name", label: tl("Service
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
{ key: "
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
621
|
+
{ key: "name", label: tl("Service name", "Nome do servi\xE7o"), type: "text", required: true, searchable: true, showInTable: true, group: "identity", span: 2 },
|
|
622
|
+
// O CÓDIGO INTERNO é como a casa chama o serviço na comanda e no relatório
|
|
623
|
+
// — e é por ele que se procura quando o nome tem três variações.
|
|
624
|
+
{ key: "internalCode", label: tl("Internal code", "C\xF3digo interno"), type: "text", showInTable: true, searchable: true, group: "identity" },
|
|
625
|
+
{
|
|
626
|
+
key: "categoryId",
|
|
627
|
+
label: tl("Category", "Categoria"),
|
|
628
|
+
type: "relation",
|
|
629
|
+
showInTable: true,
|
|
630
|
+
group: "identity",
|
|
631
|
+
relation: { table: "categories", labelField: "name", filter: { kind: "service_category" } }
|
|
632
|
+
},
|
|
633
|
+
// A COR identifica o serviço na grade da agenda, onde não cabe o nome.
|
|
634
|
+
{ key: "color", label: tl("Colour", "Cor de identifica\xE7\xE3o"), type: "color", showInTable: false, group: "identity" },
|
|
635
|
+
{ key: "description", label: tl("Description", "Descri\xE7\xE3o"), type: "textarea", showInTable: false, group: "identity", span: 2 },
|
|
636
|
+
{ key: "price", label: tl("Price", "Pre\xE7o"), type: "currency", required: true, showInTable: true, group: "pricing" },
|
|
637
|
+
{ key: "durationMinutes", label: tl("Duration (min)", "Dura\xE7\xE3o (min)"), type: "number", required: true, showInTable: true, group: "pricing" },
|
|
638
|
+
// VALIDADE em dias: quanto tempo o cliente tem para executar o que
|
|
639
|
+
// comprou. É o que faz um pacote expirar, e sem ela ele nunca expira.
|
|
640
|
+
{ key: "validityDays", label: tl("Validity (days)", "Validade (dias)"), type: "number", showInTable: false, group: "pricing" },
|
|
281
641
|
{
|
|
282
642
|
key: "isBookable",
|
|
283
643
|
label: tl("Appears in the calendar", "Aparece na agenda"),
|
|
284
644
|
type: "boolean",
|
|
285
645
|
showInTable: true,
|
|
286
646
|
defaultValue: true,
|
|
287
|
-
|
|
647
|
+
group: "pricing",
|
|
288
648
|
hint: tl(
|
|
289
649
|
"Can be picked when booking an appointment. Turn off for what is sold in a package and never booked on its own.",
|
|
290
650
|
"Pode ser escolhido ao marcar um hor\xE1rio. Desligue para o que \xE9 vendido em pacote e nunca se marca sozinho."
|
|
291
651
|
)
|
|
292
652
|
},
|
|
293
|
-
{ key: "
|
|
653
|
+
{ key: "schedulingAlertText", label: tl("Booking alert", "Alertas para agendamento"), type: "textarea", showInTable: false, group: "texts" },
|
|
654
|
+
{ key: "reminderText", label: tl("Reminder text", "Texto do lembrete"), type: "textarea", showInTable: false, group: "texts" },
|
|
655
|
+
{ key: "contractText", label: tl("Contract text", "Texto para contrato"), type: "textarea", showInTable: false, group: "texts" },
|
|
656
|
+
{ key: "postServiceText", label: tl("After-service text", "Texto de p\xF3s-atendimento"), type: "textarea", showInTable: false, group: "texts" },
|
|
657
|
+
...recordRailFields({ tagsTable: "services", activeLabel: tl("Active", "Ativo") })
|
|
294
658
|
],
|
|
295
659
|
// Services are their own Ring-0 archetype table (public.services), not a
|
|
296
660
|
// kind of product. Direct query by table+schema (no kind filter).
|
|
@@ -301,24 +665,176 @@ var serviceEntity = {
|
|
|
301
665
|
searchColumns: ["name"]
|
|
302
666
|
}
|
|
303
667
|
};
|
|
668
|
+
var professionEntity = {
|
|
669
|
+
name: tl("Profession", "Profiss\xE3o"),
|
|
670
|
+
namePlural: tl("Professions", "Profiss\xF5es"),
|
|
671
|
+
icon: "BriefcaseBusiness",
|
|
672
|
+
displayField: "name",
|
|
673
|
+
defaultSort: "sortOrder",
|
|
674
|
+
formColumns: 2,
|
|
675
|
+
facets: [ACTIVE_FACET],
|
|
676
|
+
fieldGroups: [
|
|
677
|
+
{
|
|
678
|
+
id: "identity",
|
|
679
|
+
label: tl("Identification", "Identifica\xE7\xE3o"),
|
|
680
|
+
columns: 2,
|
|
681
|
+
description: tl(
|
|
682
|
+
"The list the house keeps, so a rota does not depend on how someone typed it.",
|
|
683
|
+
"A lista que a casa mant\xE9m, para a escala n\xE3o depender de como algu\xE9m digitou."
|
|
684
|
+
)
|
|
685
|
+
},
|
|
686
|
+
{ ...RECORD_GROUP }
|
|
687
|
+
],
|
|
688
|
+
fields: [
|
|
689
|
+
{ key: "name", label: tl("Name", "Nome"), type: "text", required: true, showInTable: true, searchable: true, group: "identity", span: 2 },
|
|
690
|
+
{ key: "sortOrder", label: tl("Order", "Ordem"), type: "number", showInTable: true, defaultValue: 0, group: "identity" },
|
|
691
|
+
...recordRailFields({ tagsTable: "categories", activeLabel: tl("Active", "Ativo"), unit: false, notesInTable: true })
|
|
692
|
+
],
|
|
693
|
+
data: {
|
|
694
|
+
table: "categories",
|
|
695
|
+
schema: "public",
|
|
696
|
+
tenantScoped: true,
|
|
697
|
+
filters: { kind: "profession" },
|
|
698
|
+
defaults: { kind: "profession" },
|
|
699
|
+
searchColumns: ["name"]
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
var petSpeciesEntity = {
|
|
703
|
+
name: tl("Species", "Esp\xE9cie"),
|
|
704
|
+
namePlural: tl("Animal species", "Tipos de animais"),
|
|
705
|
+
icon: "PawPrint",
|
|
706
|
+
displayField: "name",
|
|
707
|
+
defaultSort: "sortOrder",
|
|
708
|
+
formColumns: 2,
|
|
709
|
+
facets: [ACTIVE_FACET],
|
|
710
|
+
fieldGroups: [
|
|
711
|
+
{ id: "identity", label: tl("Identification", "Identifica\xE7\xE3o"), columns: 2 },
|
|
712
|
+
{ ...RECORD_GROUP }
|
|
713
|
+
],
|
|
714
|
+
fields: [
|
|
715
|
+
{ key: "name", label: tl("Name", "Nome"), type: "text", required: true, showInTable: true, searchable: true, group: "identity", span: 2 },
|
|
716
|
+
// A ORDEM existe porque a espécie mais atendida tem de estar em cima: um
|
|
717
|
+
// hotel para aves não quer «cachorro» na primeira posição.
|
|
718
|
+
{ key: "sortOrder", label: tl("Order", "Ordem"), type: "number", showInTable: true, defaultValue: 0, group: "identity" },
|
|
719
|
+
...recordRailFields({ tagsTable: "categories", activeLabel: tl("Active", "Ativo"), unit: false })
|
|
720
|
+
],
|
|
721
|
+
data: {
|
|
722
|
+
table: "categories",
|
|
723
|
+
schema: "public",
|
|
724
|
+
tenantScoped: true,
|
|
725
|
+
filters: { kind: "pet_species" },
|
|
726
|
+
defaults: { kind: "pet_species" },
|
|
727
|
+
searchColumns: ["name"]
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
var servicePackageItemEntity = {
|
|
731
|
+
name: tl("Item", "Item"),
|
|
732
|
+
namePlural: tl("Items", "Servi\xE7os do pacote"),
|
|
733
|
+
icon: "ListChecks",
|
|
734
|
+
displayField: "productId",
|
|
735
|
+
defaultSort: "sortOrder",
|
|
736
|
+
fields: [
|
|
737
|
+
{
|
|
738
|
+
key: "productId",
|
|
739
|
+
label: tl("Service", "Servi\xE7o"),
|
|
740
|
+
type: "relation",
|
|
741
|
+
required: true,
|
|
742
|
+
showInTable: true,
|
|
743
|
+
relation: { table: "services", labelField: "name" }
|
|
744
|
+
},
|
|
745
|
+
// QUANTAS VEZES este serviço entra no pacote. «10 sessões de drenagem» é
|
|
746
|
+
// uma linha com quantidade 10, não dez linhas.
|
|
747
|
+
{ key: "quantity", label: tl("Quantity", "Quantidade"), type: "number", min: 1, defaultValue: 1, showInTable: true },
|
|
748
|
+
{ key: "sortOrder", label: tl("Order", "Ordem"), type: "number", defaultValue: 0, showInTable: true }
|
|
749
|
+
],
|
|
750
|
+
data: { table: "service_package_items", schema: "public", tenantScoped: true }
|
|
751
|
+
};
|
|
752
|
+
var servicePackageEntity = {
|
|
753
|
+
name: tl("Package", "Pacote"),
|
|
754
|
+
namePlural: tl("Service packages", "Pacotes de servi\xE7o"),
|
|
755
|
+
icon: "PackageCheck",
|
|
756
|
+
displayField: "name",
|
|
757
|
+
defaultSort: "name",
|
|
758
|
+
formColumns: 2,
|
|
759
|
+
facets: [ACTIVE_FACET],
|
|
760
|
+
fieldGroups: [
|
|
761
|
+
{ id: "identity", label: tl("Identification", "Identifica\xE7\xE3o"), columns: 1 },
|
|
762
|
+
{
|
|
763
|
+
id: "pricing",
|
|
764
|
+
label: tl("Price and validity", "Pre\xE7o e validade"),
|
|
765
|
+
columns: 2,
|
|
766
|
+
description: tl(
|
|
767
|
+
"What the client pays once, and until when they can use it.",
|
|
768
|
+
"O que o cliente paga de uma vez, e at\xE9 quando pode usar."
|
|
769
|
+
)
|
|
770
|
+
},
|
|
771
|
+
{ ...RECORD_GROUP }
|
|
772
|
+
],
|
|
773
|
+
fields: [
|
|
774
|
+
{ key: "name", label: tl("Name", "Nome"), type: "text", required: true, showInTable: true, searchable: true, group: "identity" },
|
|
775
|
+
{ key: "description", label: tl("Description", "Descri\xE7\xE3o"), type: "textarea", showInTable: false, group: "identity" },
|
|
776
|
+
{ key: "price", label: tl("Price", "Pre\xE7o"), type: "currency", required: true, showInTable: true, group: "pricing" },
|
|
777
|
+
// O TOTAL DE SESSÕES é o que o cliente comprou; os itens dizem de QUÊ.
|
|
778
|
+
// Os dois existem porque um pacote pode vender «10 sessões» escolhendo o
|
|
779
|
+
// serviço na hora, e outro pode listar exatamente o que entra.
|
|
780
|
+
{ key: "sessionsTotal", label: tl("Sessions", "Total de sess\xF5es"), type: "number", min: 1, showInTable: true, group: "pricing" },
|
|
781
|
+
// SEM VALIDADE o pacote nunca expira, e o saldo de sessões vira uma dívida
|
|
782
|
+
// sem prazo — é este número que faz o pacote vencer.
|
|
783
|
+
{
|
|
784
|
+
key: "validityDays",
|
|
785
|
+
label: tl("Validity (days)", "Validade (dias)"),
|
|
786
|
+
type: "number",
|
|
787
|
+
showInTable: true,
|
|
788
|
+
group: "pricing",
|
|
789
|
+
hint: tl("Empty means it never expires.", "Vazio significa que nunca vence.")
|
|
790
|
+
},
|
|
791
|
+
...recordRailFields({ tagsTable: "service_packages", activeLabel: tl("Active", "Ativo") })
|
|
792
|
+
],
|
|
793
|
+
relatedLists: [
|
|
794
|
+
{ entity: servicePackageItemEntity, foreignKey: "packageId", icon: "ListChecks" }
|
|
795
|
+
],
|
|
796
|
+
data: {
|
|
797
|
+
table: "service_packages",
|
|
798
|
+
schema: "public",
|
|
799
|
+
tenantScoped: true,
|
|
800
|
+
searchColumns: ["name"]
|
|
801
|
+
}
|
|
802
|
+
};
|
|
304
803
|
|
|
305
804
|
// src/index.ts
|
|
306
805
|
var SCREENS = [
|
|
307
806
|
{ id: "services", path: "/registry/services", entity: serviceEntity, icon: "Briefcase", feature: "services" },
|
|
308
807
|
{ id: "serviceCategories", path: "/registry/categories", entity: serviceCategoryEntity, icon: "Tag", feature: "services", nested: true },
|
|
808
|
+
// O pacote é catálogo como o serviço — aninhado nele, porque é um jeito de
|
|
809
|
+
// vender serviço e não um assunto novo no menu.
|
|
810
|
+
{ id: "servicePackages", path: "/registry/service-packages", entity: servicePackageEntity, icon: "PackageCheck", feature: "services", nested: true },
|
|
309
811
|
{ id: "locations", path: "/registry/locations", entity: serviceLocationEntity, icon: "MapPin", feature: "services" },
|
|
812
|
+
// A SALA, aninhada na unidade: ela é onde se atende dentro do prédio, e uma
|
|
813
|
+
// linha solta no menu ao lado de «Unidades» repetiria a confusão que a 153
|
|
814
|
+
// acabou de desfazer.
|
|
310
815
|
{ id: "locationGroups", path: "/registry/location-groups", entity: locationGroupEntity, icon: "Map", feature: "services", nested: true },
|
|
311
816
|
{ id: "staff", path: "/registry/staff", entity: staffEntity, icon: "UserCog" },
|
|
817
|
+
{ id: "teams", path: "/registry/teams", entity: teamEntity, icon: "Users", nested: true },
|
|
818
|
+
// Aninhada em Profissionais: a profissão é o vocabulário DELES, e uma linha
|
|
819
|
+
// solta no menu para uma lista de cinco palavras custa mais do que vale.
|
|
820
|
+
{ id: "professions", path: "/registry/professions", entity: professionEntity, icon: "BriefcaseBusiness", nested: true },
|
|
312
821
|
{ id: "contacts", path: "/registry/contacts", entity: contactEntity, icon: "Contact" },
|
|
313
822
|
{ id: "suppliers", path: "/registry/suppliers", entity: supplierEntity, icon: "Building2" },
|
|
314
823
|
{ id: "partnerships", path: "/registry/partnerships", entity: partnershipEntity, icon: "Handshake" },
|
|
315
824
|
{ id: "equipment", path: "/registry/equipment", entity: equipmentEntity, icon: "Wrench" },
|
|
316
|
-
{ id: "origins", path: "/registry/origins", entity: originEntity, icon: "Globe" }
|
|
825
|
+
{ id: "origins", path: "/registry/origins", entity: originEntity, icon: "Globe" },
|
|
826
|
+
// Só faz sentido em casa que atende pet — ver OPT_IN.
|
|
827
|
+
{ id: "petSpecies", path: "/registry/pet-species", entity: petSpeciesEntity, icon: "PawPrint", feature: "pets" }
|
|
317
828
|
];
|
|
829
|
+
var OPT_IN = /* @__PURE__ */ new Set(["petSpecies"]);
|
|
318
830
|
var serviceTabs = () => [
|
|
319
831
|
{ id: "services", label: serviceEntity.namePlural ?? serviceEntity.name, icon: Briefcase, onSelect: () => adminNavigateTo("/registry/services") },
|
|
320
832
|
{ id: "serviceCategories", label: serviceCategoryEntity.namePlural ?? serviceCategoryEntity.name, icon: Tag, onSelect: () => adminNavigateTo("/registry/categories") }
|
|
321
833
|
];
|
|
834
|
+
var staffTabs = () => [
|
|
835
|
+
{ id: "staff", label: staffEntity.namePlural ?? staffEntity.name, icon: UserCog, onSelect: () => adminNavigateTo("/registry/staff") },
|
|
836
|
+
{ id: "teams", label: teamEntity.namePlural ?? teamEntity.name, icon: Users, onSelect: () => adminNavigateTo("/registry/teams") }
|
|
837
|
+
];
|
|
322
838
|
var locationTabs = () => [
|
|
323
839
|
{ id: "locations", label: serviceLocationEntity.namePlural ?? serviceLocationEntity.name, icon: MapPin, onSelect: () => adminNavigateTo("/registry/locations") },
|
|
324
840
|
{ id: "locationGroups", label: locationGroupEntity.namePlural ?? locationGroupEntity.name, icon: Map, onSelect: () => adminNavigateTo("/registry/location-groups") }
|
|
@@ -326,11 +842,11 @@ var locationTabs = () => [
|
|
|
326
842
|
function createRegistryPlugin(options) {
|
|
327
843
|
const section = options?.navSection ?? "main";
|
|
328
844
|
const base = options?.navPosition ?? 80;
|
|
329
|
-
const active = SCREENS.filter((screen) => options?.modules?.[screen.id] !== false);
|
|
845
|
+
const active = SCREENS.filter((screen) => OPT_IN.has(screen.id) ? options?.modules?.[screen.id] === true : options?.modules?.[screen.id] !== false);
|
|
330
846
|
const pages = active.map((screen, index) => {
|
|
331
847
|
const entity = options?.entities?.[screen.id] ?? screen.entity;
|
|
332
848
|
const feature = screen.feature;
|
|
333
|
-
const grouped = screen.id === "services" || screen.id === "serviceCategories" ? { navTabs: serviceTabs, activeNavTab: screen.id } : screen.id === "locations" || screen.id === "locationGroups" ? { navTabs: locationTabs, activeNavTab: screen.id } : {};
|
|
849
|
+
const grouped = screen.id === "services" || screen.id === "serviceCategories" ? { navTabs: serviceTabs, activeNavTab: screen.id } : screen.id === "locations" || screen.id === "locationGroups" ? { navTabs: locationTabs, activeNavTab: screen.id } : screen.id === "staff" || screen.id === "teams" ? { navTabs: staffTabs, activeNavTab: screen.id } : {};
|
|
334
850
|
return {
|
|
335
851
|
screen,
|
|
336
852
|
entity,
|
|
@@ -369,6 +885,6 @@ function createRegistryPlugin(options) {
|
|
|
369
885
|
};
|
|
370
886
|
}
|
|
371
887
|
|
|
372
|
-
export { contactEntity, createRegistryPlugin, equipmentEntity, locationGroupEntity, originEntity, partnershipEntity, serviceCategoryEntity, serviceEntity, serviceLocationEntity, staffEntity, supplierEntity };
|
|
888
|
+
export { contactEntity, createRegistryPlugin, equipmentEntity, locationGroupEntity, originEntity, partnershipEntity, petSpeciesEntity, professionEntity, serviceCategoryEntity, serviceEntity, serviceLocationEntity, servicePackageEntity, staffEntity, supplierEntity, teamEntity };
|
|
373
889
|
//# sourceMappingURL=index.js.map
|
|
374
890
|
//# sourceMappingURL=index.js.map
|