@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
|
@@ -1,13 +1,66 @@
|
|
|
1
1
|
import type { ApiClient } from '../../core/ApiClient'
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
4
|
-
|
|
5
|
-
export class ScheduleApi extends BaseCrudApi<
|
|
2
|
+
import { BaseReadApi } from '../../core/BaseReadApi'
|
|
3
|
+
import type {
|
|
6
4
|
Schedule,
|
|
5
|
+
ScheduleRecord,
|
|
7
6
|
CreateSchedulePayload,
|
|
8
|
-
UpdateSchedulePayload
|
|
9
|
-
|
|
7
|
+
UpdateSchedulePayload,
|
|
8
|
+
} from './types'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Scheduled messages. Create answers no body and update answers the stored record, so this
|
|
12
|
+
* class extends `BaseReadApi` and writes its own create/update/delete.
|
|
13
|
+
*/
|
|
14
|
+
export class ScheduleApi extends BaseReadApi<Schedule> {
|
|
10
15
|
constructor(client: ApiClient) {
|
|
11
16
|
super(client, '/schedule')
|
|
12
17
|
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates a schedule. Resolves with NO CONTENT: find it back with `getOne` (e.g. by
|
|
21
|
+
* `contactId` and `scheduledAt`).
|
|
22
|
+
* @permissions schedule.create
|
|
23
|
+
*/
|
|
24
|
+
create(body: CreateSchedulePayload, headers?: Record<string, string>): Promise<void> {
|
|
25
|
+
return this.client.post<void, CreateSchedulePayload>(this.basePath, body, headers)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Updates a schedule. Resolves to the stored record, without relationships. An unknown id
|
|
30
|
+
* answers 500.
|
|
31
|
+
* @permissions schedule.view OR schedule.update
|
|
32
|
+
*/
|
|
33
|
+
updateById(
|
|
34
|
+
id: string,
|
|
35
|
+
body: UpdateSchedulePayload,
|
|
36
|
+
headers?: Record<string, string>,
|
|
37
|
+
): Promise<ScheduleRecord> {
|
|
38
|
+
return this.client.put<ScheduleRecord, UpdateSchedulePayload>(
|
|
39
|
+
`${this.basePath}/${id}`,
|
|
40
|
+
body,
|
|
41
|
+
headers,
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Deletes a schedule (soft delete). Resolves to `'OK'`; an unknown id answers 500.
|
|
47
|
+
* @permissions schedule.view OR schedule.destroy
|
|
48
|
+
*/
|
|
49
|
+
deleteById(id: string, headers?: Record<string, string>): Promise<'OK'> {
|
|
50
|
+
return this.client.delete<'OK'>(`${this.basePath}/${id}`, headers)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Deletes several schedules of the account. Ids that are not UUIDs, or of another account, are
|
|
55
|
+
* skipped in silence; always resolves to `'OK'`.
|
|
56
|
+
* @permissions schedule.destroy
|
|
57
|
+
*/
|
|
58
|
+
deleteMany(ids: string[], headers?: Record<string, string>): Promise<'OK'> {
|
|
59
|
+
return this.client.request<'OK', { where: { id: string[] } }>(
|
|
60
|
+
'DELETE',
|
|
61
|
+
`${this.basePath}/many`,
|
|
62
|
+
{ where: { id: ids } },
|
|
63
|
+
headers,
|
|
64
|
+
)
|
|
65
|
+
}
|
|
13
66
|
}
|
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
import type { Contact } from '../contacts/types'
|
|
2
2
|
import type { Department } from '../departments/types'
|
|
3
|
+
import type { File } from '../files/types'
|
|
3
4
|
import type { User } from '../users/types'
|
|
4
5
|
|
|
6
|
+
/** `scheduled` until the worker runs it; `error` and `not_sent` come with the `reason`. */
|
|
7
|
+
export type ScheduleStatus = 'scheduled' | 'done' | 'error' | 'not_sent'
|
|
8
|
+
|
|
5
9
|
export type Schedule = {
|
|
6
10
|
id: string
|
|
7
|
-
status:
|
|
11
|
+
status: ScheduleStatus
|
|
12
|
+
/**
|
|
13
|
+
* Why it was not sent: `ALREADY_TICKET_OPENED`, `DDD_BLOCKED`, `SERVICE_IS_NOT_ENABLED`,
|
|
14
|
+
* `CONTACT_IS_BLOCKED`, `CONTACT_NOT_EXISTS`, `HSM_LIMIT_EXCEEDED`, `HSM_IS_BLOCKED`,
|
|
15
|
+
* `SERVICE_IS_BLOCKED`, `INVALID_PAYLOAD`, a WhatsApp Business error code or
|
|
16
|
+
* `SCHEDULE_UNKNOWN_REASON`.
|
|
17
|
+
*/
|
|
8
18
|
reason: string | null
|
|
9
|
-
message
|
|
19
|
+
/** For a WhatsApp Business template (`hsmId`), the message the server built from it. */
|
|
20
|
+
message: string | null
|
|
10
21
|
notes: string
|
|
11
|
-
openTicket: boolean
|
|
12
|
-
notificateUser: boolean
|
|
22
|
+
openTicket: boolean | null
|
|
23
|
+
notificateUser: boolean | null
|
|
24
|
+
/** Sends the message even when the contact already has an open ticket. */
|
|
25
|
+
sendWithOpenTicket: boolean | null
|
|
13
26
|
extraOptions: Record<string, unknown> | null
|
|
14
27
|
hsmId: string | null
|
|
15
28
|
hsmFileId: string | null
|
|
@@ -20,39 +33,81 @@ export type Schedule = {
|
|
|
20
33
|
accountId: string
|
|
21
34
|
createdAt: string
|
|
22
35
|
updatedAt: string
|
|
23
|
-
deletedAt: string | null
|
|
24
36
|
// relationships
|
|
25
37
|
contact?: Contact
|
|
26
38
|
department?: Department
|
|
27
39
|
user?: User | null
|
|
40
|
+
files?: File[]
|
|
41
|
+
/** The header media of the WhatsApp Business template. */
|
|
42
|
+
hsmFile?: File
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The schedule as stored, without relationships: what `updateById` resolves to. */
|
|
46
|
+
export type ScheduleRecord = Omit<
|
|
47
|
+
Schedule,
|
|
48
|
+
'contact' | 'department' | 'user' | 'files' | 'hsmFile'
|
|
49
|
+
> & {
|
|
50
|
+
deletedAt: string | null
|
|
28
51
|
}
|
|
29
52
|
|
|
30
|
-
export type ScheduleRelationships =
|
|
53
|
+
export type ScheduleRelationships =
|
|
54
|
+
| 'contact'
|
|
55
|
+
| 'department'
|
|
56
|
+
| 'user'
|
|
57
|
+
| 'files'
|
|
58
|
+
| 'hsmFile'
|
|
59
|
+
| 'contact.service'
|
|
60
|
+
| 'contact.person'
|
|
61
|
+
|
|
62
|
+
/** A new attachment. */
|
|
63
|
+
export type ScheduleNewFilePayload = { base64: string; name: string; mimetype: string }
|
|
31
64
|
|
|
65
|
+
/** On update: a new attachment, or one to keep (`id`). */
|
|
66
|
+
export type ScheduleFilePayload = ScheduleNewFilePayload | { id: string }
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Another schedule of the connection less than 5 minutes apart answers 402
|
|
70
|
+
* (`Schedule time is already exist.`). A contact of another account answers 500.
|
|
71
|
+
*/
|
|
32
72
|
export type CreateSchedulePayload = {
|
|
33
73
|
scheduledAt: string
|
|
34
74
|
contactId: string
|
|
35
75
|
departmentId: string
|
|
36
|
-
|
|
76
|
+
/** Up to 1024 characters. Optional for a WhatsApp Business template, built from `hsmId`. */
|
|
77
|
+
message?: string
|
|
78
|
+
/**
|
|
79
|
+
* Up to 1024 characters. Required by the route only with `openTicket: true`, but the column is
|
|
80
|
+
* NOT NULL: send it always (an empty string is fine).
|
|
81
|
+
*/
|
|
37
82
|
notes: string
|
|
38
|
-
openTicket
|
|
83
|
+
openTicket?: boolean
|
|
39
84
|
notificateUser?: boolean
|
|
85
|
+
sendWithOpenTicket?: boolean
|
|
40
86
|
userId?: string
|
|
41
87
|
hsmId?: string
|
|
42
88
|
hsmFileId?: string
|
|
43
89
|
extraOptions?: Record<string, unknown>
|
|
90
|
+
/** New attachments only: the create drops `{ id }` items. */
|
|
91
|
+
files?: ScheduleNewFilePayload[]
|
|
44
92
|
}
|
|
45
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Not partial: `scheduledAt`, `contactId` and `departmentId` are required on update too. The
|
|
96
|
+
* 5-minute conflict (402) is checked against the whole account.
|
|
97
|
+
*/
|
|
46
98
|
export type UpdateSchedulePayload = {
|
|
47
|
-
scheduledAt
|
|
48
|
-
contactId
|
|
49
|
-
departmentId
|
|
99
|
+
scheduledAt: string
|
|
100
|
+
contactId: string
|
|
101
|
+
departmentId: string
|
|
50
102
|
userId?: string | null
|
|
51
103
|
message?: string
|
|
52
104
|
notes?: string
|
|
53
105
|
openTicket?: boolean
|
|
54
106
|
notificateUser?: boolean
|
|
107
|
+
sendWithOpenTicket?: boolean
|
|
55
108
|
hsmId?: string | null
|
|
56
109
|
hsmFileId?: string | null
|
|
57
110
|
extraOptions?: Record<string, unknown> | null
|
|
111
|
+
/** Replaces the attachments: `{ id }` keeps one, `{ base64, ... }` adds one; omitted or `[]` removes all. */
|
|
112
|
+
files?: ScheduleFilePayload[]
|
|
58
113
|
}
|
|
@@ -55,4 +55,44 @@ describe('TicketTopicsApi', () => {
|
|
|
55
55
|
await unarchive('t1')
|
|
56
56
|
expect(client.post).toHaveBeenCalledTimes(2)
|
|
57
57
|
})
|
|
58
|
+
|
|
59
|
+
it('create sends the department ids', async () => {
|
|
60
|
+
await api.create({ name: 'Dúvida', departments: ['d1'] })
|
|
61
|
+
expect(client.post).toHaveBeenCalledWith(
|
|
62
|
+
'/ticket-topics',
|
|
63
|
+
{ name: 'Dúvida', departments: ['d1'] },
|
|
64
|
+
undefined,
|
|
65
|
+
)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('getMany sends the department filters at the top of the query', async () => {
|
|
69
|
+
const query = { departmentIds: ['d1'], includeNoDepartment: true }
|
|
70
|
+
await api.getMany(query)
|
|
71
|
+
expect(client.get).toHaveBeenCalledWith(
|
|
72
|
+
`/ticket-topics?query=${encodeURIComponent(JSON.stringify(query))}`,
|
|
73
|
+
undefined,
|
|
74
|
+
)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('deleteById resolves to the OK of the route', async () => {
|
|
78
|
+
vi.mocked(client.delete).mockResolvedValue('OK')
|
|
79
|
+
expect(await api.deleteById('t1')).toBe('OK')
|
|
80
|
+
expect(client.delete).toHaveBeenCalledWith('/ticket-topics/t1', undefined)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('getOne reads one item of the paginated list, where the department filters apply', async () => {
|
|
84
|
+
vi.mocked(client.get).mockResolvedValue({ data: [{ id: 't1' }], total: 1 })
|
|
85
|
+
expect(await api.getOne({ departmentIds: ['d1'] })).toEqual({ id: 't1' })
|
|
86
|
+
expect(client.get).toHaveBeenCalledWith(
|
|
87
|
+
`/ticket-topics?query=${encodeURIComponent(
|
|
88
|
+
JSON.stringify({ departmentIds: ['d1'], paginate: true, perPage: 1, page: 1 }),
|
|
89
|
+
)}`,
|
|
90
|
+
undefined,
|
|
91
|
+
)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('getOne resolves null on an empty page', async () => {
|
|
95
|
+
vi.mocked(client.get).mockResolvedValue({ data: [], total: 0 })
|
|
96
|
+
expect(await api.getOne()).toBeNull()
|
|
97
|
+
})
|
|
58
98
|
})
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import type { ApiClient } from '../../core/ApiClient'
|
|
2
2
|
import { BaseCrudApi } from '../../core/BaseCrudApi'
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
TicketTopic,
|
|
5
|
+
TicketTopicListQuery,
|
|
6
|
+
CreateTicketTopicPayload,
|
|
7
|
+
UpdateTicketTopicPayload,
|
|
8
|
+
} from './types'
|
|
4
9
|
|
|
5
10
|
export class TicketTopicsApi extends BaseCrudApi<
|
|
6
11
|
TicketTopic,
|
|
7
12
|
CreateTicketTopicPayload,
|
|
8
|
-
UpdateTicketTopicPayload
|
|
13
|
+
UpdateTicketTopicPayload,
|
|
14
|
+
TicketTopicListQuery,
|
|
15
|
+
'OK'
|
|
9
16
|
> {
|
|
10
17
|
constructor(client: ApiClient) {
|
|
11
18
|
super(client, '/ticket-topics')
|
|
@@ -13,10 +20,34 @@ export class TicketTopicsApi extends BaseCrudApi<
|
|
|
13
20
|
this.unarchive = this.unarchive.bind(this)
|
|
14
21
|
}
|
|
15
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Returns the first topic matching the query, or `null` when none match. Reads one item of the
|
|
25
|
+
* paginated list (instead of `paginate: false`), the only path where `departmentIds` and
|
|
26
|
+
* `includeNoDepartment` apply.
|
|
27
|
+
* @permissions ticketTopics.view
|
|
28
|
+
*/
|
|
29
|
+
override async getOne(
|
|
30
|
+
query?: TicketTopicListQuery,
|
|
31
|
+
headers?: Record<string, string>,
|
|
32
|
+
): Promise<TicketTopic | null> {
|
|
33
|
+
const page = await this.getMany({ ...query, paginate: true, perPage: 1, page: 1 }, headers)
|
|
34
|
+
return page?.data?.[0] ?? null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Deletes a ticket topic (soft delete). Resolves to `'OK'`; an unknown id answers 500.
|
|
39
|
+
* @permissions ticketTopics.view OR ticketTopics.destroy
|
|
40
|
+
*/
|
|
41
|
+
override deleteById(id: string, headers?: Record<string, string>): Promise<'OK'> {
|
|
42
|
+
return super.deleteById(id, headers)
|
|
43
|
+
}
|
|
44
|
+
|
|
16
45
|
/**
|
|
17
46
|
* Archives a ticket topic. Unlike most resources, ticket-topics archive through a
|
|
18
47
|
* dedicated endpoint (POST `${basePath}/${id}/archive`) rather than `updateById`.
|
|
19
|
-
*
|
|
48
|
+
* Resolves to the stored topic, without `departments`. A topic used in a bot answers 403;
|
|
49
|
+
* an unknown id answers 500.
|
|
50
|
+
* @permissions ticketTopics.archive
|
|
20
51
|
*/
|
|
21
52
|
archive(id: string, headers?: Record<string, string>): Promise<TicketTopic> {
|
|
22
53
|
return this.client.post<TicketTopic>(
|
|
@@ -28,7 +59,7 @@ export class TicketTopicsApi extends BaseCrudApi<
|
|
|
28
59
|
|
|
29
60
|
/**
|
|
30
61
|
* Restores a previously archived ticket topic via the same dedicated endpoint.
|
|
31
|
-
* @permissions
|
|
62
|
+
* @permissions ticketTopics.archive
|
|
32
63
|
*/
|
|
33
64
|
unarchive(id: string, headers?: Record<string, string>): Promise<TicketTopic> {
|
|
34
65
|
return this.client.post<TicketTopic>(
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { ListQuery } from '../../core/types'
|
|
2
|
+
import type { Department } from '../departments/types'
|
|
3
|
+
|
|
1
4
|
export type TicketTopic = {
|
|
2
5
|
id: string
|
|
3
6
|
name: string
|
|
@@ -6,12 +9,34 @@ export type TicketTopic = {
|
|
|
6
9
|
createdAt: string
|
|
7
10
|
updatedAt: string
|
|
8
11
|
deletedAt: string | null
|
|
12
|
+
// relationships
|
|
13
|
+
/** Always comes in the paginated `getMany`; elsewhere only with `include`. Empty = all departments. */
|
|
14
|
+
departments?: Department[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type TicketTopicRelationships = 'departments'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Department filters of the paginated `getMany` and of `getOne`. `getMany({ paginate: false })`
|
|
21
|
+
* ignores them in silence (the route reads them only on the paginated path).
|
|
22
|
+
*/
|
|
23
|
+
export type TicketTopicListQuery = ListQuery<TicketTopic> & {
|
|
24
|
+
/** Topics linked to any of these departments. */
|
|
25
|
+
departmentIds?: string | string[]
|
|
26
|
+
/** Also (or only) the topics linked to no department. Only with the default `'json'` query format. */
|
|
27
|
+
includeNoDepartment?: boolean
|
|
9
28
|
}
|
|
10
29
|
|
|
11
30
|
export type CreateTicketTopicPayload = {
|
|
31
|
+
/** Unique in the account (409, unless the account allows duplicate names); trimmed. */
|
|
12
32
|
name: string
|
|
33
|
+
/** Department ids; `[]` = all departments. Required: a missing list answers 500 after creating the topic. */
|
|
34
|
+
departments: string[]
|
|
13
35
|
}
|
|
14
36
|
|
|
15
37
|
export type UpdateTicketTopicPayload = {
|
|
16
|
-
|
|
38
|
+
/** Required on update too. */
|
|
39
|
+
name: string
|
|
40
|
+
/** Department ids, replacing the current ones; omitted keeps them. */
|
|
41
|
+
departments?: string[]
|
|
17
42
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
2
|
import { TicketsApi } from './TicketsApi'
|
|
3
3
|
import type { ApiClient } from '../../core/ApiClient'
|
|
4
|
+
import type { TicketsListQuery } from './types'
|
|
4
5
|
|
|
5
6
|
// ── Mock client factory ──────────────────────────────────────────────
|
|
6
7
|
|
|
@@ -18,6 +19,8 @@ function createMockClient(): ApiClient {
|
|
|
18
19
|
|
|
19
20
|
// ── Tests ────────────────────────────────────────────────────────────
|
|
20
21
|
|
|
22
|
+
const headers = { 'X-Custom': 'value' }
|
|
23
|
+
|
|
21
24
|
describe('TicketsApi', () => {
|
|
22
25
|
let client: ReturnType<typeof createMockClient>
|
|
23
26
|
let api: TicketsApi
|
|
@@ -29,18 +32,28 @@ describe('TicketsApi', () => {
|
|
|
29
32
|
|
|
30
33
|
describe('closeMany', () => {
|
|
31
34
|
it('PUTs /tickets/close-many with explicit ids', async () => {
|
|
32
|
-
vi.mocked(client.put).mockResolvedValue(
|
|
35
|
+
vi.mocked(client.put).mockResolvedValue(undefined)
|
|
33
36
|
const result = await api.closeMany({ ids: ['t1', 't2'] })
|
|
34
37
|
expect(client.put).toHaveBeenCalledWith(
|
|
35
38
|
'/tickets/close-many',
|
|
36
39
|
{ ids: ['t1', 't2'] },
|
|
37
40
|
undefined,
|
|
38
41
|
)
|
|
39
|
-
expect(result).
|
|
42
|
+
expect(result).toBeUndefined()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('sends the subjects and the summary of the closing', async () => {
|
|
46
|
+
vi.mocked(client.put).mockResolvedValue(undefined)
|
|
47
|
+
await api.closeMany({ ids: ['t1'], ticketTopicIds: ['tt1'], comments: 'Resolved' })
|
|
48
|
+
expect(client.put).toHaveBeenCalledWith(
|
|
49
|
+
'/tickets/close-many',
|
|
50
|
+
{ ids: ['t1'], ticketTopicIds: ['tt1'], comments: 'Resolved' },
|
|
51
|
+
undefined,
|
|
52
|
+
)
|
|
40
53
|
})
|
|
41
54
|
|
|
42
55
|
it('PUTs /tickets/close-many with a JSON-stringified query for the select-all variant', async () => {
|
|
43
|
-
vi.mocked(client.put).mockResolvedValue(
|
|
56
|
+
vi.mocked(client.put).mockResolvedValue(undefined)
|
|
44
57
|
await api.closeMany({ allContactsSelected: true, query: { query: '{"where":{}}' } })
|
|
45
58
|
expect(client.put).toHaveBeenCalledWith(
|
|
46
59
|
'/tickets/close-many',
|
|
@@ -50,8 +63,7 @@ describe('TicketsApi', () => {
|
|
|
50
63
|
})
|
|
51
64
|
|
|
52
65
|
it('forwards custom headers', async () => {
|
|
53
|
-
vi.mocked(client.put).mockResolvedValue(
|
|
54
|
-
const headers = { 'X-Custom': 'value' }
|
|
66
|
+
vi.mocked(client.put).mockResolvedValue(undefined)
|
|
55
67
|
await api.closeMany({ ids: ['t1'] }, headers)
|
|
56
68
|
expect(client.put).toHaveBeenCalledWith('/tickets/close-many', { ids: ['t1'] }, headers)
|
|
57
69
|
})
|
|
@@ -61,4 +73,129 @@ describe('TicketsApi', () => {
|
|
|
61
73
|
await expect(api.closeMany({ ids: ['t1'] })).rejects.toThrow('Forbidden')
|
|
62
74
|
})
|
|
63
75
|
})
|
|
76
|
+
|
|
77
|
+
describe('deprecated CRUD', () => {
|
|
78
|
+
it('keeps create, updateById and deleteById on the generic routes', async () => {
|
|
79
|
+
vi.mocked(client.post).mockResolvedValue({})
|
|
80
|
+
vi.mocked(client.put).mockResolvedValue({})
|
|
81
|
+
vi.mocked(client.delete).mockResolvedValue({})
|
|
82
|
+
await api.create({ contactId: 'c1', departmentId: 'd1' })
|
|
83
|
+
await api.updateById('t1', { comments: 'x' })
|
|
84
|
+
await api.deleteById('t1')
|
|
85
|
+
expect(client.post).toHaveBeenCalledWith(
|
|
86
|
+
'/tickets',
|
|
87
|
+
{ contactId: 'c1', departmentId: 'd1' },
|
|
88
|
+
undefined,
|
|
89
|
+
)
|
|
90
|
+
expect(client.put).toHaveBeenCalledWith('/tickets/t1', { comments: 'x' }, undefined)
|
|
91
|
+
expect(client.delete).toHaveBeenCalledWith('/tickets/t1', undefined)
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
describe('count', () => {
|
|
96
|
+
it('GETs /tickets/count with the query', async () => {
|
|
97
|
+
vi.mocked(client.get).mockResolvedValue({ count: 3 })
|
|
98
|
+
const query = { where: { isOpen: true } }
|
|
99
|
+
const result = await api.count(query)
|
|
100
|
+
expect(client.get).toHaveBeenCalledWith(
|
|
101
|
+
`/tickets/count?query=${encodeURIComponent(JSON.stringify(query))}`,
|
|
102
|
+
undefined,
|
|
103
|
+
)
|
|
104
|
+
expect(result).toEqual({ count: 3 })
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('GETs /tickets/count without a query', async () => {
|
|
108
|
+
vi.mocked(client.get).mockResolvedValue({ count: 10 })
|
|
109
|
+
await api.count()
|
|
110
|
+
expect(client.get).toHaveBeenCalledWith('/tickets/count', undefined)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('sends the queries for one count per query', async () => {
|
|
114
|
+
vi.mocked(client.get).mockResolvedValue({ counts: [1, 2] })
|
|
115
|
+
const query = { queries: [{ where: { isOpen: true } }, { where: { isOpen: false } }] }
|
|
116
|
+
const result = await api.count(query, headers)
|
|
117
|
+
expect(client.get).toHaveBeenCalledWith(
|
|
118
|
+
`/tickets/count?query=${encodeURIComponent(JSON.stringify(query))}`,
|
|
119
|
+
headers,
|
|
120
|
+
)
|
|
121
|
+
expect(result).toEqual({ counts: [1, 2] })
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
describe('countPost', () => {
|
|
126
|
+
it('POSTs /tickets/count with the filters and the query', async () => {
|
|
127
|
+
vi.mocked(client.post).mockResolvedValue({ count: 3 })
|
|
128
|
+
const body = { where: { isOpen: true }, query: {} }
|
|
129
|
+
const result = await api.countPost(body, headers)
|
|
130
|
+
expect(client.post).toHaveBeenCalledWith('/tickets/count', body, headers)
|
|
131
|
+
expect(result).toEqual({ count: 3 })
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('POSTs the queries for one count per query', async () => {
|
|
135
|
+
vi.mocked(client.post).mockResolvedValue({ counts: [3, 0] })
|
|
136
|
+
const body = {
|
|
137
|
+
query: { queries: [{ where: { isOpen: true } }, { where: { isOpen: false } }] },
|
|
138
|
+
}
|
|
139
|
+
const result = await api.countPost(body)
|
|
140
|
+
expect(client.post).toHaveBeenCalledWith('/tickets/count', body, undefined)
|
|
141
|
+
expect(result).toEqual({ counts: [3, 0] })
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
describe('open ticket counts', () => {
|
|
146
|
+
it('GETs /tickets/count-open/{serviceId}', async () => {
|
|
147
|
+
vi.mocked(client.get).mockResolvedValue(3)
|
|
148
|
+
expect(await api.countOpenByService('s1', headers)).toBe(3)
|
|
149
|
+
expect(client.get).toHaveBeenCalledWith('/tickets/count-open/s1', headers)
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('GETs /tickets/count-open-by-department/{departmentId}', async () => {
|
|
153
|
+
vi.mocked(client.get).mockResolvedValue(2)
|
|
154
|
+
expect(await api.countOpenByDepartment('d1')).toBe(2)
|
|
155
|
+
expect(client.get).toHaveBeenCalledWith('/tickets/count-open-by-department/d1', undefined)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('GETs /tickets/count-open-by-user/{userId}', async () => {
|
|
159
|
+
vi.mocked(client.get).mockResolvedValue(0)
|
|
160
|
+
expect(await api.countOpenByUser('u1')).toBe(0)
|
|
161
|
+
expect(client.get).toHaveBeenCalledWith('/tickets/count-open-by-user/u1', undefined)
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
describe('getFirstWaitingTime', () => {
|
|
166
|
+
it('GETs /tickets/{id}/first-waiting-time', async () => {
|
|
167
|
+
vi.mocked(client.get).mockResolvedValue('00:01:30')
|
|
168
|
+
expect(await api.getFirstWaitingTime('t1', headers)).toBe('00:01:30')
|
|
169
|
+
expect(client.get).toHaveBeenCalledWith('/tickets/t1/first-waiting-time', headers)
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
describe('exportTxt', () => {
|
|
174
|
+
it('POSTs /tickets/export with the protocol', async () => {
|
|
175
|
+
vi.mocked(client.post).mockResolvedValue('Nome do contato: A')
|
|
176
|
+
expect(await api.exportTxt('202610061675', headers)).toBe('Nome do contato: A')
|
|
177
|
+
expect(client.post).toHaveBeenCalledWith(
|
|
178
|
+
'/tickets/export',
|
|
179
|
+
{ protocol: '202610061675' },
|
|
180
|
+
headers,
|
|
181
|
+
)
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
describe('exportHistory', () => {
|
|
186
|
+
it('POSTs /tickets/export-history with the query in the URL and no body', async () => {
|
|
187
|
+
vi.mocked(client.post).mockResolvedValue('Protocolo;Nome\n')
|
|
188
|
+
const query: TicketsListQuery = {
|
|
189
|
+
where: { isOpen: true },
|
|
190
|
+
include: [{ model: 'contact', include: ['service'] }],
|
|
191
|
+
order: [['startedAt', 'DESC']],
|
|
192
|
+
}
|
|
193
|
+
expect(await api.exportHistory(query)).toBe('Protocolo;Nome\n')
|
|
194
|
+
expect(client.post).toHaveBeenCalledWith(
|
|
195
|
+
`/tickets/export-history?query=${encodeURIComponent(JSON.stringify(query))}`,
|
|
196
|
+
undefined,
|
|
197
|
+
undefined,
|
|
198
|
+
)
|
|
199
|
+
})
|
|
200
|
+
})
|
|
64
201
|
})
|