@ikatec/digisac-api-sdk 4.5.1 → 4.7.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/README.md +16 -11
- package/dist/apis/answers/AnswersApi.cjs +38 -0
- package/dist/apis/answers/AnswersApi.cjs.map +1 -1
- package/dist/apis/answers/AnswersApi.d.ts +33 -2
- package/dist/apis/answers/AnswersApi.d.ts.map +1 -1
- package/dist/apis/answers/AnswersApi.mjs +38 -0
- package/dist/apis/answers/AnswersApi.mjs.map +1 -1
- package/dist/apis/answers/types.d.ts +64 -1
- package/dist/apis/answers/types.d.ts.map +1 -1
- package/dist/apis/botSimulators/BotSimulatorsApi.cjs +42 -0
- package/dist/apis/botSimulators/BotSimulatorsApi.cjs.map +1 -0
- package/dist/apis/botSimulators/BotSimulatorsApi.d.ts +31 -0
- package/dist/apis/botSimulators/BotSimulatorsApi.d.ts.map +1 -0
- package/dist/apis/botSimulators/BotSimulatorsApi.mjs +41 -0
- package/dist/apis/botSimulators/BotSimulatorsApi.mjs.map +1 -0
- package/dist/apis/botSimulators/index.cjs +4 -0
- package/dist/apis/botSimulators/index.d.ts +3 -0
- package/dist/apis/botSimulators/index.d.ts.map +1 -0
- package/dist/apis/botSimulators/index.mjs +3 -0
- package/dist/apis/botSimulators/types.cjs +0 -0
- package/dist/apis/botSimulators/types.d.ts +73 -0
- package/dist/apis/botSimulators/types.d.ts.map +1 -0
- package/dist/apis/botSimulators/types.mjs +0 -0
- package/dist/apis/botVersions/BotVersionsApi.cjs +25 -0
- package/dist/apis/botVersions/BotVersionsApi.cjs.map +1 -1
- package/dist/apis/botVersions/BotVersionsApi.d.ts +18 -0
- package/dist/apis/botVersions/BotVersionsApi.d.ts.map +1 -1
- package/dist/apis/botVersions/BotVersionsApi.mjs +25 -0
- package/dist/apis/botVersions/BotVersionsApi.mjs.map +1 -1
- package/dist/apis/botVersions/types.d.ts +4 -0
- package/dist/apis/botVersions/types.d.ts.map +1 -1
- package/dist/apis/bots/BotsApi.cjs +99 -4
- package/dist/apis/bots/BotsApi.cjs.map +1 -1
- package/dist/apis/bots/BotsApi.d.ts +78 -5
- package/dist/apis/bots/BotsApi.d.ts.map +1 -1
- package/dist/apis/bots/BotsApi.mjs +99 -4
- package/dist/apis/bots/BotsApi.mjs.map +1 -1
- package/dist/apis/bots/types.d.ts +266 -16
- package/dist/apis/bots/types.d.ts.map +1 -1
- package/dist/apis/index.cjs +3 -0
- package/dist/apis/index.d.ts +1 -0
- package/dist/apis/index.d.ts.map +1 -1
- package/dist/apis/index.mjs +3 -1
- package/dist/apis/questions/QuestionsApi.cjs +15 -0
- package/dist/apis/questions/QuestionsApi.cjs.map +1 -1
- package/dist/apis/questions/QuestionsApi.d.ts +15 -1
- package/dist/apis/questions/QuestionsApi.d.ts.map +1 -1
- package/dist/apis/questions/QuestionsApi.mjs +15 -0
- package/dist/apis/questions/QuestionsApi.mjs.map +1 -1
- package/dist/apis/questions/types.d.ts +25 -5
- package/dist/apis/questions/types.d.ts.map +1 -1
- package/dist/apis/quickReplies/QuickRepliesApi.cjs +16 -0
- package/dist/apis/quickReplies/QuickRepliesApi.cjs.map +1 -1
- package/dist/apis/quickReplies/QuickRepliesApi.d.ts +15 -2
- package/dist/apis/quickReplies/QuickRepliesApi.d.ts.map +1 -1
- package/dist/apis/quickReplies/QuickRepliesApi.mjs +16 -0
- package/dist/apis/quickReplies/QuickRepliesApi.mjs.map +1 -1
- package/dist/apis/quickReplies/types.d.ts +30 -2
- package/dist/apis/quickReplies/types.d.ts.map +1 -1
- package/dist/apis/schedule/ScheduleApi.cjs +37 -2
- package/dist/apis/schedule/ScheduleApi.cjs.map +1 -1
- package/dist/apis/schedule/ScheduleApi.d.ts +30 -3
- package/dist/apis/schedule/ScheduleApi.d.ts.map +1 -1
- package/dist/apis/schedule/ScheduleApi.mjs +37 -2
- package/dist/apis/schedule/ScheduleApi.mjs.map +1 -1
- package/dist/apis/schedule/types.d.ts +58 -11
- package/dist/apis/schedule/types.d.ts.map +1 -1
- package/dist/apis/ticketTopics/TicketTopicsApi.cjs +25 -2
- package/dist/apis/ticketTopics/TicketTopicsApi.cjs.map +1 -1
- package/dist/apis/ticketTopics/TicketTopicsApi.d.ts +18 -4
- package/dist/apis/ticketTopics/TicketTopicsApi.d.ts.map +1 -1
- package/dist/apis/ticketTopics/TicketTopicsApi.mjs +25 -2
- package/dist/apis/ticketTopics/TicketTopicsApi.mjs.map +1 -1
- package/dist/apis/ticketTopics/types.d.ts +22 -1
- package/dist/apis/ticketTopics/types.d.ts.map +1 -1
- package/dist/apis/tickets/TicketsApi.cjs +74 -1
- package/dist/apis/tickets/TicketsApi.cjs.map +1 -1
- package/dist/apis/tickets/TicketsApi.d.ts +68 -3
- package/dist/apis/tickets/TicketsApi.d.ts.map +1 -1
- package/dist/apis/tickets/TicketsApi.mjs +74 -1
- package/dist/apis/tickets/TicketsApi.mjs.map +1 -1
- package/dist/apis/tickets/types.d.ts +69 -8
- package/dist/apis/tickets/types.d.ts.map +1 -1
- package/dist/core/BaseCrudApi.cjs +5 -2
- package/dist/core/BaseCrudApi.cjs.map +1 -1
- package/dist/core/BaseCrudApi.d.ts +7 -4
- package/dist/core/BaseCrudApi.d.ts.map +1 -1
- package/dist/core/BaseCrudApi.mjs +5 -2
- package/dist/core/BaseCrudApi.mjs.map +1 -1
- package/dist/core/types.d.ts +1 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/incommingWebhooks/index.d.ts +17 -9
- package/dist/incommingWebhooks/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/apis/answers/AnswersApi.test.ts +67 -0
- package/src/apis/answers/AnswersApi.ts +66 -2
- package/src/apis/answers/types.ts +64 -2
- package/src/apis/botSimulators/BotSimulatorsApi.test.ts +58 -0
- package/src/apis/botSimulators/BotSimulatorsApi.ts +57 -0
- package/src/apis/botSimulators/index.ts +2 -0
- package/src/apis/botSimulators/types.ts +72 -0
- package/src/apis/botVersions/BotVersionsApi.test.ts +51 -0
- package/src/apis/botVersions/BotVersionsApi.ts +41 -0
- package/src/apis/botVersions/types.ts +4 -0
- package/src/apis/bots/BotsApi.test.ts +177 -0
- package/src/apis/bots/BotsApi.ts +162 -6
- package/src/apis/bots/types.ts +267 -15
- package/src/apis/index.ts +1 -0
- package/src/apis/payloads.test-d.ts +178 -1
- package/src/apis/queryFormat.test.ts +23 -0
- package/src/apis/questions/QuestionsApi.test.ts +49 -0
- package/src/apis/questions/QuestionsApi.ts +20 -1
- package/src/apis/questions/types.ts +25 -6
- package/src/apis/quickReplies/QuickRepliesApi.test.ts +69 -0
- package/src/apis/quickReplies/QuickRepliesApi.ts +35 -2
- package/src/apis/quickReplies/types.ts +34 -2
- package/src/apis/schedule/ScheduleApi.test.ts +80 -0
- package/src/apis/schedule/ScheduleApi.ts +59 -6
- package/src/apis/schedule/types.ts +66 -11
- package/src/apis/ticketTopics/TicketTopicsApi.test.ts +40 -0
- package/src/apis/ticketTopics/TicketTopicsApi.ts +35 -4
- package/src/apis/ticketTopics/types.ts +26 -1
- package/src/apis/tickets/TicketsApi.test.ts +142 -5
- package/src/apis/tickets/TicketsApi.ts +135 -3
- package/src/apis/tickets/types.ts +76 -6
- package/src/core/BaseCrudApi.ts +9 -5
- package/src/core/types.ts +2 -1
- package/src/incommingWebhooks/index.test-d.ts +23 -1
- package/src/incommingWebhooks/index.ts +18 -9
package/src/apis/index.ts
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
import { expectAssignable, expectError, expectNotAssignable, expectType } from 'tsd'
|
|
2
2
|
import type { ApiClient } from '../core/ApiClient'
|
|
3
|
+
import type { Paginated } from '../core/types'
|
|
4
|
+
import { AnswersApi } from './answers/AnswersApi'
|
|
5
|
+
import type { AnswersOverview } from './answers/types'
|
|
6
|
+
import { BotSimulatorsApi } from './botSimulators/BotSimulatorsApi'
|
|
7
|
+
import { BotsApi } from './bots/BotsApi'
|
|
8
|
+
import type { Bot, BotContextInput, BotParsedContext } from './bots/types'
|
|
3
9
|
import { CardsApi } from './cards/CardsApi'
|
|
4
10
|
import { ContactsApi } from './contacts/ContactsApi'
|
|
5
11
|
import { KanbanColumnsApi } from './kanbanColumns/KanbanColumnsApi'
|
|
6
12
|
import { PipelineApi } from './pipeline/PipelineApi'
|
|
7
13
|
import type { PermissionName } from './permissions/types'
|
|
8
14
|
import { PersonalAccessTokensApi } from './personalAccessTokens/PersonalAccessTokensApi'
|
|
15
|
+
import { QuestionsApi } from './questions/QuestionsApi'
|
|
16
|
+
import { QuickRepliesApi } from './quickReplies/QuickRepliesApi'
|
|
9
17
|
import { RolesApi } from './roles/RolesApi'
|
|
18
|
+
import { ScheduleApi } from './schedule/ScheduleApi'
|
|
19
|
+
import type { ScheduleRecord } from './schedule/types'
|
|
10
20
|
import { TagsApi } from './tags/TagsApi'
|
|
21
|
+
import { TicketTopicsApi } from './ticketTopics/TicketTopicsApi'
|
|
11
22
|
import { TicketsApi } from './tickets/TicketsApi'
|
|
12
|
-
import type { Ticket, TicketTransfer } from './tickets/types'
|
|
23
|
+
import type { Ticket, TicketTransfer, TicketsCount, TicketsCounts } from './tickets/types'
|
|
13
24
|
import { TimetableApi } from './timetable/TimetableApi'
|
|
14
25
|
import { UsersApi } from './users/UsersApi'
|
|
15
26
|
|
|
@@ -115,3 +126,169 @@ expectType<TicketTransfer[] | undefined>(ticket.ticketTransfers)
|
|
|
115
126
|
declare const transfer: TicketTransfer
|
|
116
127
|
expectType<string | null>(transfer.byUserId)
|
|
117
128
|
expectType<'opened' | 'transferred' | 'closed' | null>(transfer.action)
|
|
129
|
+
|
|
130
|
+
// ── tickets: the counts, the first waiting time and the exports ─────
|
|
131
|
+
|
|
132
|
+
expectType<Promise<TicketsCount>>(tickets.count({ where: { isOpen: true } }))
|
|
133
|
+
expectType<Promise<TicketsCounts>>(tickets.count({ queries: [{ where: { isOpen: true } }] }))
|
|
134
|
+
expectType<Promise<TicketsCount>>(tickets.countPost({ where: { isOpen: true }, query: {} }))
|
|
135
|
+
expectType<Promise<TicketsCounts>>(
|
|
136
|
+
tickets.countPost({ query: { queries: [{ where: { isOpen: false } }] } }),
|
|
137
|
+
)
|
|
138
|
+
// `query` is required: without it the route answers 500
|
|
139
|
+
expectError(tickets.countPost({ where: { isOpen: true } }))
|
|
140
|
+
expectType<Promise<number>>(tickets.countOpenByService('s-1'))
|
|
141
|
+
expectType<Promise<number>>(tickets.countOpenByDepartment('d-1'))
|
|
142
|
+
expectType<Promise<number>>(tickets.countOpenByUser('u-1'))
|
|
143
|
+
expectType<Promise<string>>(tickets.getFirstWaitingTime('t-1'))
|
|
144
|
+
expectType<Promise<string>>(tickets.exportTxt('202610061675'))
|
|
145
|
+
expectType<Promise<string>>(
|
|
146
|
+
tickets.exportHistory({ include: [{ model: 'contact', include: ['service'] }] }),
|
|
147
|
+
)
|
|
148
|
+
expectType<Promise<void>>(tickets.closeMany({ ids: ['t-1'], comments: 'Resolved' }))
|
|
149
|
+
expectType<Promise<void>>(
|
|
150
|
+
tickets.closeMany({
|
|
151
|
+
allContactsSelected: true,
|
|
152
|
+
query: { query: '{}' },
|
|
153
|
+
ticketTopicIds: ['tt-1'],
|
|
154
|
+
}),
|
|
155
|
+
)
|
|
156
|
+
tickets.getById('t-1', { include: ['ticketTopics', 'firstMessage', 'account'] })
|
|
157
|
+
expectType<string | undefined>(ticket.metrics.maxWaitingTimeDepartmentId)
|
|
158
|
+
|
|
159
|
+
// ── bots: the /bots routes take and return the contexts as a list ───
|
|
160
|
+
|
|
161
|
+
const bots = new BotsApi(client)
|
|
162
|
+
const botContexts: BotContextInput[] = [
|
|
163
|
+
{
|
|
164
|
+
id: '@INIT',
|
|
165
|
+
triggers: [
|
|
166
|
+
{
|
|
167
|
+
trigger: { type: 'MESSAGE_RECEIVED' },
|
|
168
|
+
rules: [
|
|
169
|
+
{ conditions: [], actions: [{ type: { value: 'SEND_MESSAGE' }, data: { text: 'Oi' } }] },
|
|
170
|
+
],
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
},
|
|
174
|
+
]
|
|
175
|
+
bots.create({ name: 'Bot', contexts: botContexts, flowJson: { nodes: [], edges: [] } })
|
|
176
|
+
expectError(bots.create({ name: 'Bot', contexts: botContexts, flowJson: { nodes: [] } }))
|
|
177
|
+
expectError(bots.create({ name: 'Bot' }))
|
|
178
|
+
expectError(bots.create({ name: 'Bot', contexts: { '@INIT': { name: 'Início', triggers: {} } } }))
|
|
179
|
+
expectError(
|
|
180
|
+
bots.create({
|
|
181
|
+
name: 'Bot',
|
|
182
|
+
contexts: [
|
|
183
|
+
{
|
|
184
|
+
id: '@INIT',
|
|
185
|
+
triggers: [
|
|
186
|
+
{
|
|
187
|
+
trigger: { type: 'MESSAGE_RECEIVED' },
|
|
188
|
+
rules: [
|
|
189
|
+
{
|
|
190
|
+
conditions: [],
|
|
191
|
+
actions: [{ type: { value: 'SET_VAR' }, data: { key: 'k', value: 'v' } }],
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
}),
|
|
199
|
+
)
|
|
200
|
+
bots.updateById('b-1', { name: 'Bot', contexts: botContexts, isDraft: true })
|
|
201
|
+
bots.updateById('b-1', { name: 'Bot', onlyUpdateBotName: true })
|
|
202
|
+
bots.create({
|
|
203
|
+
name: 'Bot',
|
|
204
|
+
contexts: [
|
|
205
|
+
{
|
|
206
|
+
id: '@INIT',
|
|
207
|
+
triggers: [
|
|
208
|
+
{
|
|
209
|
+
trigger: { type: 'TICKET_OPENED' },
|
|
210
|
+
rules: [
|
|
211
|
+
{
|
|
212
|
+
conditions: [],
|
|
213
|
+
actions: [
|
|
214
|
+
{ type: { value: 'SEND_TERM' }, data: { acceptanceTerms: { id: 'term-1' } } },
|
|
215
|
+
],
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
})
|
|
223
|
+
expectError(bots.updateById('b-1', { contexts: botContexts }))
|
|
224
|
+
expectError(bots.updateById('b-1', { name: 'Bot' }))
|
|
225
|
+
expectType<Promise<Paginated<Bot>>>(bots.getMany({ paginate: false }))
|
|
226
|
+
expectType<Promise<'OK' | false>>(bots.deleteById('b-1'))
|
|
227
|
+
declare const bot: Bot
|
|
228
|
+
expectType<BotParsedContext[]>(bot.contexts)
|
|
229
|
+
|
|
230
|
+
// ── bot simulator: the trigger pick needs the id of the selection message ──
|
|
231
|
+
|
|
232
|
+
const simulator = new BotSimulatorsApi(client)
|
|
233
|
+
simulator.sendEvent('b-1', { type: 'text_contact', text: 'oi' })
|
|
234
|
+
simulator.sendEvent('b-1', { type: 'select_trigger', id: 'm-1', selectedValue: 'TICKET_OPENED' })
|
|
235
|
+
expectError(simulator.sendEvent('b-1', { type: 'select_trigger', selectedValue: 'TICKET_OPENED' }))
|
|
236
|
+
expectError(
|
|
237
|
+
simulator.sendEvent('b-1', { type: 'select_condition', id: 'm-1', selectedValue: 'yes' }),
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
// ── questions: duration goes as a string; update is not partial ─────
|
|
241
|
+
|
|
242
|
+
const questions = new QuestionsApi(client)
|
|
243
|
+
const question = {
|
|
244
|
+
name: 'NPS',
|
|
245
|
+
type: 'nps',
|
|
246
|
+
questionMessage: 'De 0 a 10?',
|
|
247
|
+
duration: '60',
|
|
248
|
+
tries: 3,
|
|
249
|
+
successMessage: 'Obrigado!',
|
|
250
|
+
invalidMessage: 'De 0 a 10, por favor',
|
|
251
|
+
} as const
|
|
252
|
+
questions.create(question)
|
|
253
|
+
expectType<Promise<'OK'>>(questions.deleteById('q-1'))
|
|
254
|
+
expectError(questions.create({ ...question, duration: 60 }))
|
|
255
|
+
questions.updateById('q-1', { type: 'nps', questionMessage: 'x', duration: 60, tries: 3 })
|
|
256
|
+
expectError(questions.updateById('q-1', { name: 'NPS' }))
|
|
257
|
+
|
|
258
|
+
// ── answers: the overview NPS is positional ─────────────────────────
|
|
259
|
+
|
|
260
|
+
const answers = new AnswersApi(client)
|
|
261
|
+
expectType<Promise<AnswersOverview>>(answers.overview({ type: 'nps', periodType: 'closeDate' }))
|
|
262
|
+
expectError(answers.overview({ type: 'custom' }))
|
|
263
|
+
expectType<Promise<'OK'>>(answers.deleteById('a-1'))
|
|
264
|
+
|
|
265
|
+
// ── schedule: create answers no body; update is not partial ─────────
|
|
266
|
+
|
|
267
|
+
const schedule = new ScheduleApi(client)
|
|
268
|
+
const scheduled = {
|
|
269
|
+
scheduledAt: '2026-10-02T12:00:00Z',
|
|
270
|
+
contactId: 'c-1',
|
|
271
|
+
departmentId: 'd-1',
|
|
272
|
+
notes: '',
|
|
273
|
+
}
|
|
274
|
+
expectType<Promise<void>>(schedule.create({ ...scheduled, message: 'Oi' }))
|
|
275
|
+
expectType<Promise<ScheduleRecord>>(schedule.updateById('s-1', scheduled))
|
|
276
|
+
expectError(schedule.updateById('s-1', { message: 'Oi' }))
|
|
277
|
+
expectError(schedule.create({ ...scheduled, files: [{ name: 'a.png' }] }))
|
|
278
|
+
expectError(schedule.create({ ...scheduled, files: [{ id: 'f-1' }] }))
|
|
279
|
+
schedule.updateById('s-1', { ...scheduled, files: [{ id: 'f-1' }] })
|
|
280
|
+
|
|
281
|
+
// ── ticket topics: create needs the departments ─────────────────────
|
|
282
|
+
|
|
283
|
+
const topics = new TicketTopicsApi(client)
|
|
284
|
+
topics.create({ name: 'Dúvida', departments: [] })
|
|
285
|
+
expectError(topics.create({ name: 'Dúvida' }))
|
|
286
|
+
expectError(topics.updateById('t-1', { departments: [] }))
|
|
287
|
+
expectType<Promise<'OK'>>(topics.deleteById('t-1'))
|
|
288
|
+
|
|
289
|
+
// ── quick replies: update fails without the files ───────────────────
|
|
290
|
+
|
|
291
|
+
const quickReplies = new QuickRepliesApi(client)
|
|
292
|
+
quickReplies.updateById('r-1', { title: 'Oi', text: 'Olá', files: [] })
|
|
293
|
+
expectError(quickReplies.updateById('r-1', { title: 'Oi', text: 'Olá' }))
|
|
294
|
+
expectType<Promise<'OK'>>(quickReplies.deleteById('r-1'))
|
|
@@ -11,6 +11,7 @@ import { PermissionsApi } from './permissions/PermissionsApi'
|
|
|
11
11
|
import { ServiceAccessManagementApi } from './serviceAccessManagement/ServiceAccessManagementApi'
|
|
12
12
|
import { StickerUsersApi } from './stickerUsers/StickerUsersApi'
|
|
13
13
|
import { TermsApi } from './terms/TermsApi'
|
|
14
|
+
import { TicketsApi } from './tickets/TicketsApi'
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Every method that sends a list/get query must serialize it in the client's `queryFormat` and
|
|
@@ -34,6 +35,10 @@ const listQuery = { where: { name: 'x' }, page: 2, perPage: 5 }
|
|
|
34
35
|
const getQuery = { attributes: ['id'] }
|
|
35
36
|
/** `Permission.name` is a union of the catalog names, so the permissions case filters by a real one. */
|
|
36
37
|
const permissionsQuery = { where: { name: 'chat' as const }, page: 2, perPage: 5 }
|
|
38
|
+
const ticketsQuery = {
|
|
39
|
+
where: { isOpen: true },
|
|
40
|
+
order: [['startedAt', 'DESC'] as ['startedAt', 'DESC']],
|
|
41
|
+
}
|
|
37
42
|
|
|
38
43
|
type Case = {
|
|
39
44
|
name: string
|
|
@@ -273,6 +278,24 @@ const cases: Case[] = [
|
|
|
273
278
|
return (f ? api.withQueryFormat(f) : api).getById('t1', { attributes: ['id'] })
|
|
274
279
|
},
|
|
275
280
|
},
|
|
281
|
+
{
|
|
282
|
+
name: 'TicketsApi.count',
|
|
283
|
+
path: '/tickets/count',
|
|
284
|
+
query: ticketsQuery,
|
|
285
|
+
call: (client, f) => {
|
|
286
|
+
const api = new TicketsApi(client)
|
|
287
|
+
return (f ? api.withQueryFormat(f) : api).count(ticketsQuery)
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
name: 'TicketsApi.exportHistory',
|
|
292
|
+
path: '/tickets/export-history',
|
|
293
|
+
query: ticketsQuery,
|
|
294
|
+
call: (client, f) => {
|
|
295
|
+
const api = new TicketsApi(client)
|
|
296
|
+
return (f ? api.withQueryFormat(f) : api).exportHistory(ticketsQuery)
|
|
297
|
+
},
|
|
298
|
+
},
|
|
276
299
|
]
|
|
277
300
|
|
|
278
301
|
const jsonUrl = (path: string, query: object) =>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import type { ApiClient } from '../../core/ApiClient'
|
|
3
|
+
import { QuestionsApi } from './QuestionsApi'
|
|
4
|
+
|
|
5
|
+
// ── Mock client factory ──────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
function createMockClient(): ApiClient {
|
|
8
|
+
return {
|
|
9
|
+
setAccessToken: vi.fn().mockReturnThis(),
|
|
10
|
+
request: vi.fn(),
|
|
11
|
+
get: vi.fn(),
|
|
12
|
+
post: vi.fn(),
|
|
13
|
+
put: vi.fn(),
|
|
14
|
+
patch: vi.fn(),
|
|
15
|
+
delete: vi.fn(),
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ── Tests ────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
describe('QuestionsApi', () => {
|
|
22
|
+
let client: ReturnType<typeof createMockClient>
|
|
23
|
+
let api: QuestionsApi
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
client = createMockClient()
|
|
27
|
+
api = new QuestionsApi(client)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('create sends duration as a string', async () => {
|
|
31
|
+
const body = {
|
|
32
|
+
name: 'NPS',
|
|
33
|
+
type: 'nps',
|
|
34
|
+
questionMessage: 'De 0 a 10?',
|
|
35
|
+
duration: '60',
|
|
36
|
+
tries: 3,
|
|
37
|
+
successMessage: 'Obrigado!',
|
|
38
|
+
invalidMessage: 'De 0 a 10',
|
|
39
|
+
} as const
|
|
40
|
+
await api.create(body)
|
|
41
|
+
expect(client.post).toHaveBeenCalledWith('/questions', body, undefined)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('deleteById resolves to the OK of the route', async () => {
|
|
45
|
+
vi.mocked(client.delete).mockResolvedValue('OK')
|
|
46
|
+
expect(await api.deleteById('q1')).toBe('OK')
|
|
47
|
+
expect(client.delete).toHaveBeenCalledWith('/questions/q1', undefined)
|
|
48
|
+
})
|
|
49
|
+
})
|
|
@@ -1,13 +1,32 @@
|
|
|
1
1
|
import type { ApiClient } from '../../core/ApiClient'
|
|
2
2
|
import { BaseCrudApi } from '../../core/BaseCrudApi'
|
|
3
|
+
import type { ListQuery } from '../../core/types'
|
|
3
4
|
import type { Question, CreateQuestionPayload, UpdateQuestionPayload } from './types'
|
|
4
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Evaluation questions (NPS/CSAT), sent to the contact by the `SEND_QUESTION` bot action. The
|
|
8
|
+
* backend also serves the same routes under the singular `/question` (kept for compatibility).
|
|
9
|
+
*
|
|
10
|
+
* Permissions: `evaluation.view` to read; `evaluation.view` OR `evaluation.create`/`update`/
|
|
11
|
+
* `destroy` to write.
|
|
12
|
+
*/
|
|
5
13
|
export class QuestionsApi extends BaseCrudApi<
|
|
6
14
|
Question,
|
|
7
15
|
CreateQuestionPayload,
|
|
8
|
-
UpdateQuestionPayload
|
|
16
|
+
UpdateQuestionPayload,
|
|
17
|
+
ListQuery<Question>,
|
|
18
|
+
'OK'
|
|
9
19
|
> {
|
|
10
20
|
constructor(client: ApiClient) {
|
|
11
21
|
super(client, '/questions')
|
|
12
22
|
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Deletes a question (soft delete). Resolves to `'OK'`. A question used in a bot answers 403;
|
|
26
|
+
* the answers of a deleted question leave the overview and the exports.
|
|
27
|
+
* @permissions evaluation.view OR evaluation.destroy
|
|
28
|
+
*/
|
|
29
|
+
override deleteById(id: string, headers?: Record<string, string>): Promise<'OK'> {
|
|
30
|
+
return super.deleteById(id, headers)
|
|
31
|
+
}
|
|
13
32
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Answer } from '../answers/types'
|
|
2
2
|
|
|
3
|
+
/** `custom` exists in the database, but it never takes an answer (the UI hides it). */
|
|
3
4
|
export type QuestionType = 'nps' | 'csat' | 'custom'
|
|
4
5
|
|
|
5
6
|
export type Question = {
|
|
@@ -7,39 +8,57 @@ export type Question = {
|
|
|
7
8
|
name: string
|
|
8
9
|
type: QuestionType
|
|
9
10
|
questionMessage: string
|
|
11
|
+
/** Minutes the contact has to answer. Comes back as a number, although it is sent as a string. */
|
|
10
12
|
duration: number
|
|
11
13
|
tries: number
|
|
12
14
|
successMessage: string
|
|
13
15
|
invalidMessage: string
|
|
14
16
|
reasonMessage: string | null
|
|
17
|
+
/** Sends `closureMessage` after the evaluation ends. */
|
|
18
|
+
sendClosureMessage: boolean
|
|
19
|
+
closureMessage: string | null
|
|
15
20
|
accountId: string
|
|
16
21
|
createdAt: string
|
|
17
22
|
updatedAt: string
|
|
18
23
|
deletedAt: string | null
|
|
19
|
-
|
|
24
|
+
/** @deprecated Never comes: the route drops every `include` (empty whitelist). */
|
|
20
25
|
answers?: Answer[]
|
|
21
26
|
}
|
|
22
27
|
|
|
28
|
+
/** @deprecated The route drops every `include` (empty whitelist). */
|
|
23
29
|
export type QuestionRelationships = 'answers'
|
|
24
30
|
|
|
25
31
|
export type CreateQuestionPayload = {
|
|
32
|
+
/** Unique in the account (409, unless the account allows duplicate names). */
|
|
26
33
|
name: string
|
|
34
|
+
/** An unknown type answers 500 (no validation before the database). */
|
|
27
35
|
type: QuestionType
|
|
28
36
|
questionMessage: string
|
|
29
|
-
|
|
37
|
+
/** Minutes, AS A STRING: a number answers 400 (`Should be of type string.`). */
|
|
38
|
+
duration: string
|
|
39
|
+
/** Greater than 0. */
|
|
30
40
|
tries: number
|
|
41
|
+
/** Not validated by the route: missing answers 500. */
|
|
31
42
|
successMessage: string
|
|
43
|
+
/** Not validated by the route: missing answers 500. */
|
|
32
44
|
invalidMessage: string
|
|
33
45
|
reasonMessage?: string
|
|
46
|
+
sendClosureMessage?: boolean
|
|
47
|
+
/** Required when `sendClosureMessage` is true (400); up to 500 characters. */
|
|
48
|
+
closureMessage?: string
|
|
34
49
|
}
|
|
35
50
|
|
|
51
|
+
/** Not partial: `type`, `questionMessage`, `duration` and `tries` are required on update. */
|
|
36
52
|
export type UpdateQuestionPayload = {
|
|
53
|
+
type: QuestionType
|
|
54
|
+
questionMessage: string
|
|
55
|
+
/** Minutes; on update a number is also taken. */
|
|
56
|
+
duration: string | number
|
|
57
|
+
tries: number
|
|
37
58
|
name?: string
|
|
38
|
-
type?: QuestionType
|
|
39
|
-
questionMessage?: string
|
|
40
|
-
duration?: number
|
|
41
|
-
tries?: number
|
|
42
59
|
successMessage?: string
|
|
43
60
|
invalidMessage?: string
|
|
44
61
|
reasonMessage?: string | null
|
|
62
|
+
sendClosureMessage?: boolean
|
|
63
|
+
closureMessage?: string | null
|
|
45
64
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import type { ApiClient } from '../../core/ApiClient'
|
|
3
|
+
import { QuickRepliesApi } from './QuickRepliesApi'
|
|
4
|
+
import type { QuickRepliesLoadOnDemandQuery } from './types'
|
|
5
|
+
|
|
6
|
+
// ── Mock client factory ──────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
function createMockClient(): ApiClient {
|
|
9
|
+
return {
|
|
10
|
+
setAccessToken: vi.fn().mockReturnThis(),
|
|
11
|
+
request: vi.fn(),
|
|
12
|
+
get: vi.fn(),
|
|
13
|
+
post: vi.fn(),
|
|
14
|
+
put: vi.fn(),
|
|
15
|
+
patch: vi.fn(),
|
|
16
|
+
delete: vi.fn(),
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const headers = { 'X-Custom': 'value' }
|
|
21
|
+
|
|
22
|
+
// ── Tests ────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
describe('QuickRepliesApi', () => {
|
|
25
|
+
let client: ReturnType<typeof createMockClient>
|
|
26
|
+
let api: QuickRepliesApi
|
|
27
|
+
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
client = createMockClient()
|
|
30
|
+
api = new QuickRepliesApi(client)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('loadOnDemand calls client.get with /quick-replies/load-on-demand and the query', async () => {
|
|
34
|
+
const query: QuickRepliesLoadOnDemandQuery = { order: [['title', 'ASC']], limit: 20, offset: 0 }
|
|
35
|
+
vi.mocked(client.get).mockResolvedValue({ data: [], total: 0 })
|
|
36
|
+
expect(await api.loadOnDemand(query, headers)).toEqual({
|
|
37
|
+
data: [],
|
|
38
|
+
total: 0,
|
|
39
|
+
})
|
|
40
|
+
expect(client.get).toHaveBeenCalledWith(
|
|
41
|
+
`/quick-replies/load-on-demand?query=${encodeURIComponent(JSON.stringify(query))}`,
|
|
42
|
+
headers,
|
|
43
|
+
)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('create sends the departments, categories and files', async () => {
|
|
47
|
+
const body = {
|
|
48
|
+
title: 'Saudação',
|
|
49
|
+
text: 'Olá!',
|
|
50
|
+
departmentIds: ['d1'],
|
|
51
|
+
categoryIds: ['c1'],
|
|
52
|
+
files: [{ id: 'f1' }],
|
|
53
|
+
}
|
|
54
|
+
await api.create(body)
|
|
55
|
+
expect(client.post).toHaveBeenCalledWith('/quick-replies', body, undefined)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('updateById sends the whole reply', async () => {
|
|
59
|
+
const body = { title: 'Saudação', text: 'Oi!', files: [] }
|
|
60
|
+
await api.updateById('q1', body, headers)
|
|
61
|
+
expect(client.put).toHaveBeenCalledWith('/quick-replies/q1', body, headers)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('deleteById resolves to the OK of the route', async () => {
|
|
65
|
+
vi.mocked(client.delete).mockResolvedValue('OK')
|
|
66
|
+
expect(await api.deleteById('q1')).toBe('OK')
|
|
67
|
+
expect(client.delete).toHaveBeenCalledWith('/quick-replies/q1', undefined)
|
|
68
|
+
})
|
|
69
|
+
})
|
|
@@ -1,13 +1,46 @@
|
|
|
1
1
|
import type { ApiClient } from '../../core/ApiClient'
|
|
2
2
|
import { BaseCrudApi } from '../../core/BaseCrudApi'
|
|
3
|
-
import type {
|
|
3
|
+
import type { ListQuery } from '../../core/types'
|
|
4
|
+
import type {
|
|
5
|
+
QuickReply,
|
|
6
|
+
QuickRepliesLoadOnDemandQuery,
|
|
7
|
+
QuickRepliesLoadOnDemandResult,
|
|
8
|
+
CreateQuickReplyPayload,
|
|
9
|
+
UpdateQuickReplyPayload,
|
|
10
|
+
} from './types'
|
|
4
11
|
|
|
5
12
|
export class QuickRepliesApi extends BaseCrudApi<
|
|
6
13
|
QuickReply,
|
|
7
14
|
CreateQuickReplyPayload,
|
|
8
|
-
UpdateQuickReplyPayload
|
|
15
|
+
UpdateQuickReplyPayload,
|
|
16
|
+
ListQuery<QuickReply>,
|
|
17
|
+
'OK'
|
|
9
18
|
> {
|
|
10
19
|
constructor(client: ApiClient) {
|
|
11
20
|
super(client, '/quick-replies')
|
|
12
21
|
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Deletes a quick reply (soft delete). Resolves to `'OK'`; an unknown id answers 500.
|
|
25
|
+
* @permissions quickReplies.view OR quickReplies.destroy
|
|
26
|
+
*/
|
|
27
|
+
override deleteById(id: string, headers?: Record<string, string>): Promise<'OK'> {
|
|
28
|
+
return super.deleteById(id, headers)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Lists the replies the user can use (no department, or one of the departments of the user),
|
|
33
|
+
* the way the chat loads them: `limit`/`offset` instead of pages, and `{ data, total }`
|
|
34
|
+
* instead of the pagination envelope.
|
|
35
|
+
* @permissions quickReplies.view
|
|
36
|
+
*/
|
|
37
|
+
loadOnDemand(
|
|
38
|
+
query?: QuickRepliesLoadOnDemandQuery,
|
|
39
|
+
headers?: Record<string, string>,
|
|
40
|
+
): Promise<QuickRepliesLoadOnDemandResult> {
|
|
41
|
+
return this.client.get<QuickRepliesLoadOnDemandResult>(
|
|
42
|
+
`${this.basePath}/load-on-demand${this.toQueryString(query)}`,
|
|
43
|
+
headers,
|
|
44
|
+
)
|
|
45
|
+
}
|
|
13
46
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ListQuery } from '../../core/types'
|
|
1
2
|
import type { Category } from '../categories/types'
|
|
2
3
|
import type { Department } from '../departments/types'
|
|
3
4
|
import type { File } from '../files/types'
|
|
@@ -11,6 +12,7 @@ export type QuickReply = {
|
|
|
11
12
|
updatedAt: string
|
|
12
13
|
deletedAt: string | null
|
|
13
14
|
// relationships
|
|
15
|
+
/** Empty = all departments. */
|
|
14
16
|
departments?: Department[]
|
|
15
17
|
categories?: Category[]
|
|
16
18
|
files?: File[]
|
|
@@ -18,12 +20,42 @@ export type QuickReply = {
|
|
|
18
20
|
|
|
19
21
|
export type QuickReplyRelationships = 'departments' | 'categories' | 'files'
|
|
20
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Files already uploaded through `FilesApi` with `attachedType: 'quickreply.file'` — uploaded
|
|
25
|
+
* with another type they get the `attachedId` but never show in `files`.
|
|
26
|
+
*/
|
|
27
|
+
export type QuickReplyFilePayload = { id: string }
|
|
28
|
+
|
|
29
|
+
/** `title` and `text` are each unique in the account (409, unless the account allows duplicate names). */
|
|
21
30
|
export type CreateQuickReplyPayload = {
|
|
22
31
|
title: string
|
|
23
32
|
text: string
|
|
33
|
+
/** Department ids; omitted = all departments. */
|
|
34
|
+
departmentIds?: string[]
|
|
35
|
+
categoryIds?: string[]
|
|
36
|
+
files?: QuickReplyFilePayload[]
|
|
24
37
|
}
|
|
25
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Not partial: `title` and `text` are required, the departments and categories are replaced
|
|
41
|
+
* (omitted = none), and so are the files.
|
|
42
|
+
*/
|
|
26
43
|
export type UpdateQuickReplyPayload = {
|
|
27
|
-
title
|
|
28
|
-
text
|
|
44
|
+
title: string
|
|
45
|
+
text: string
|
|
46
|
+
departmentIds?: string[]
|
|
47
|
+
categoryIds?: string[]
|
|
48
|
+
/** The files to keep or attach; `[]` removes all. Required: omitted, the update fails (500). */
|
|
49
|
+
files: QuickReplyFilePayload[]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The query of `loadOnDemand`, passed to the database as is. */
|
|
53
|
+
export type QuickRepliesLoadOnDemandQuery = Pick<
|
|
54
|
+
ListQuery<QuickReply>,
|
|
55
|
+
'where' | 'include' | 'order' | 'attributes' | 'limit' | 'offset'
|
|
56
|
+
>
|
|
57
|
+
|
|
58
|
+
export type QuickRepliesLoadOnDemandResult = {
|
|
59
|
+
data: QuickReply[]
|
|
60
|
+
total: number
|
|
29
61
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import type { ApiClient } from '../../core/ApiClient'
|
|
3
|
+
import { ScheduleApi } from './ScheduleApi'
|
|
4
|
+
|
|
5
|
+
// ── Mock client factory ──────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
function createMockClient(): ApiClient {
|
|
8
|
+
return {
|
|
9
|
+
setAccessToken: vi.fn().mockReturnThis(),
|
|
10
|
+
request: vi.fn(),
|
|
11
|
+
get: vi.fn(),
|
|
12
|
+
post: vi.fn(),
|
|
13
|
+
put: vi.fn(),
|
|
14
|
+
patch: vi.fn(),
|
|
15
|
+
delete: vi.fn(),
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const headers = { 'X-Custom': 'value' }
|
|
20
|
+
const body = {
|
|
21
|
+
scheduledAt: '2026-10-02T12:00:00.000Z',
|
|
22
|
+
contactId: 'c1',
|
|
23
|
+
departmentId: 'd1',
|
|
24
|
+
message: 'Lembrete',
|
|
25
|
+
notes: '',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ── Tests ────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
describe('ScheduleApi', () => {
|
|
31
|
+
let client: ReturnType<typeof createMockClient>
|
|
32
|
+
let api: ScheduleApi
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
client = createMockClient()
|
|
36
|
+
api = new ScheduleApi(client)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('create calls client.post with /schedule and resolves with no content', async () => {
|
|
40
|
+
vi.mocked(client.post).mockResolvedValue(undefined)
|
|
41
|
+
expect(await api.create(body, headers)).toBeUndefined()
|
|
42
|
+
expect(client.post).toHaveBeenCalledWith('/schedule', body, headers)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('updateById calls client.put with /schedule/:id', async () => {
|
|
46
|
+
await api.updateById('s1', { ...body, files: [{ id: 'f1' }] })
|
|
47
|
+
expect(client.put).toHaveBeenCalledWith(
|
|
48
|
+
'/schedule/s1',
|
|
49
|
+
{ ...body, files: [{ id: 'f1' }] },
|
|
50
|
+
undefined,
|
|
51
|
+
)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('deleteById calls client.delete with /schedule/:id', async () => {
|
|
55
|
+
vi.mocked(client.delete).mockResolvedValue('OK')
|
|
56
|
+
expect(await api.deleteById('s1', headers)).toBe('OK')
|
|
57
|
+
expect(client.delete).toHaveBeenCalledWith('/schedule/s1', headers)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('deleteMany sends the ids in where.id of a DELETE body', async () => {
|
|
61
|
+
vi.mocked(client.request).mockResolvedValue('OK')
|
|
62
|
+
expect(await api.deleteMany(['s1', 's2'], headers)).toBe('OK')
|
|
63
|
+
expect(client.request).toHaveBeenCalledWith(
|
|
64
|
+
'DELETE',
|
|
65
|
+
'/schedule/many',
|
|
66
|
+
{ where: { id: ['s1', 's2'] } },
|
|
67
|
+
headers,
|
|
68
|
+
)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('reads through the inherited getMany', async () => {
|
|
72
|
+
await api.getMany({ where: { contactId: 'c1' }, paginate: false })
|
|
73
|
+
expect(client.get).toHaveBeenCalledWith(
|
|
74
|
+
`/schedule?query=${encodeURIComponent(
|
|
75
|
+
JSON.stringify({ where: { contactId: 'c1' }, paginate: false }),
|
|
76
|
+
)}`,
|
|
77
|
+
undefined,
|
|
78
|
+
)
|
|
79
|
+
})
|
|
80
|
+
})
|