@ikatec/digisac-api-sdk 4.2.0 → 4.4.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.
Files changed (66) hide show
  1. package/dist/apis/contacts/ContactsApi.cjs +70 -0
  2. package/dist/apis/contacts/ContactsApi.cjs.map +1 -1
  3. package/dist/apis/contacts/ContactsApi.d.ts +51 -1
  4. package/dist/apis/contacts/ContactsApi.d.ts.map +1 -1
  5. package/dist/apis/contacts/ContactsApi.mjs +70 -0
  6. package/dist/apis/contacts/ContactsApi.mjs.map +1 -1
  7. package/dist/apis/contacts/types.d.ts +141 -1
  8. package/dist/apis/contacts/types.d.ts.map +1 -1
  9. package/dist/apis/dashboard/DashboardApi.cjs +36 -1
  10. package/dist/apis/dashboard/DashboardApi.cjs.map +1 -1
  11. package/dist/apis/dashboard/DashboardApi.d.ts +55 -2
  12. package/dist/apis/dashboard/DashboardApi.d.ts.map +1 -1
  13. package/dist/apis/dashboard/DashboardApi.mjs +36 -1
  14. package/dist/apis/dashboard/DashboardApi.mjs.map +1 -1
  15. package/dist/apis/dashboard/types.d.ts +71 -0
  16. package/dist/apis/dashboard/types.d.ts.map +1 -1
  17. package/dist/apis/services/ServicesApi.cjs +8 -0
  18. package/dist/apis/services/ServicesApi.cjs.map +1 -1
  19. package/dist/apis/services/ServicesApi.d.ts +7 -1
  20. package/dist/apis/services/ServicesApi.d.ts.map +1 -1
  21. package/dist/apis/services/ServicesApi.mjs +8 -0
  22. package/dist/apis/services/ServicesApi.mjs.map +1 -1
  23. package/dist/apis/services/types.d.ts +7 -0
  24. package/dist/apis/services/types.d.ts.map +1 -1
  25. package/dist/apis/tags/TagsApi.cjs +23 -0
  26. package/dist/apis/tags/TagsApi.cjs.map +1 -1
  27. package/dist/apis/tags/TagsApi.d.ts +19 -1
  28. package/dist/apis/tags/TagsApi.d.ts.map +1 -1
  29. package/dist/apis/tags/TagsApi.mjs +23 -0
  30. package/dist/apis/tags/TagsApi.mjs.map +1 -1
  31. package/dist/apis/tags/types.d.ts +8 -0
  32. package/dist/apis/tags/types.d.ts.map +1 -1
  33. package/dist/apis/users/UsersApi.cjs +77 -0
  34. package/dist/apis/users/UsersApi.cjs.map +1 -1
  35. package/dist/apis/users/UsersApi.d.ts +59 -1
  36. package/dist/apis/users/UsersApi.d.ts.map +1 -1
  37. package/dist/apis/users/UsersApi.mjs +77 -0
  38. package/dist/apis/users/UsersApi.mjs.map +1 -1
  39. package/dist/apis/users/types.d.ts +102 -0
  40. package/dist/apis/users/types.d.ts.map +1 -1
  41. package/package.json +1 -1
  42. package/src/apis/acceptanceTerms/AcceptanceTermsApi.test.ts +1 -1
  43. package/src/apis/authHistory/AuthHistoryApi.test.ts +1 -1
  44. package/src/apis/campaigns/CampaignsApi.test.ts +1 -1
  45. package/src/apis/contacts/ContactsApi.test.ts +182 -5
  46. package/src/apis/contacts/ContactsApi.ts +128 -0
  47. package/src/apis/contacts/types.ts +154 -1
  48. package/src/apis/dashboard/DashboardApi.test-d.ts +49 -0
  49. package/src/apis/dashboard/DashboardApi.test.ts +197 -9
  50. package/src/apis/dashboard/DashboardApi.ts +130 -2
  51. package/src/apis/dashboard/types.ts +78 -0
  52. package/src/apis/messages/MessagesApi.test.ts +3 -3
  53. package/src/apis/payloads.test-d.ts +26 -0
  54. package/src/apis/queryFormat.test.ts +91 -3
  55. package/src/apis/serviceAccessManagement/ServiceAccessManagementApi.test.ts +2 -2
  56. package/src/apis/services/ServicesApi.test.ts +49 -0
  57. package/src/apis/services/ServicesApi.ts +10 -1
  58. package/src/apis/services/types.ts +9 -0
  59. package/src/apis/stickerUsers/StickerUsersApi.test.ts +1 -1
  60. package/src/apis/tags/TagsApi.test.ts +79 -0
  61. package/src/apis/tags/TagsApi.ts +57 -1
  62. package/src/apis/tags/types.ts +9 -0
  63. package/src/apis/terms/TermsApi.test.ts +1 -1
  64. package/src/apis/users/UsersApi.test.ts +228 -0
  65. package/src/apis/users/UsersApi.ts +152 -1
  66. package/src/apis/users/types.ts +121 -0
@@ -85,7 +85,7 @@ describe('MessagesApi', () => {
85
85
  it('serializes query parameters', async () => {
86
86
  vi.mocked(client.get).mockResolvedValue(paginated)
87
87
  await api.getMany({ page: 2, perPage: 5 })
88
- const url = vi.mocked(client.get).mock.calls[0][0] as string
88
+ const url = vi.mocked(client.get).mock.calls[0]?.[0] as string
89
89
  expect(url).toContain('/messages?')
90
90
  expect(decodeURIComponent(url)).toContain('"page":2')
91
91
  expect(decodeURIComponent(url)).toContain('"perPage":5')
@@ -382,7 +382,7 @@ describe('MessagesApi', () => {
382
382
  it('appends limit query parameter when provided', async () => {
383
383
  vi.mocked(client.get).mockResolvedValue({ outOfRange: true })
384
384
  await api.outOfRange('msg-1', 50)
385
- const url = vi.mocked(client.get).mock.calls[0][0] as string
385
+ const url = vi.mocked(client.get).mock.calls[0]?.[0] as string
386
386
  expect(url).toContain('/messages/msg-1/out-of-range?')
387
387
  expect(url).toContain('limit=50')
388
388
  })
@@ -426,7 +426,7 @@ describe('MessagesApi', () => {
426
426
  it('appends limit query parameter when provided', async () => {
427
427
  vi.mocked(client.get).mockResolvedValue([])
428
428
  await api.around('msg-1', { limit: 10 })
429
- const url = vi.mocked(client.get).mock.calls[0][0] as string
429
+ const url = vi.mocked(client.get).mock.calls[0]?.[0] as string
430
430
  expect(url).toContain('/messages/msg-1/around?')
431
431
  expect(url).toContain('limit=10')
432
432
  })
@@ -1,8 +1,11 @@
1
1
  import { expectError } from 'tsd'
2
2
  import type { ApiClient } from '../core/ApiClient'
3
+ import { ContactsApi } from './contacts/ContactsApi'
3
4
  import { PersonalAccessTokensApi } from './personalAccessTokens/PersonalAccessTokensApi'
4
5
  import { RolesApi } from './roles/RolesApi'
6
+ import { TagsApi } from './tags/TagsApi'
5
7
  import { TimetableApi } from './timetable/TimetableApi'
8
+ import { UsersApi } from './users/UsersApi'
6
9
 
7
10
  declare const client: ApiClient
8
11
 
@@ -37,3 +40,26 @@ tokens.create({ name: 'CI', expiresIn: '90d' })
37
40
  expectError(tokens.create({ name: 'CI', accessTokenExpiresAt: '2030-01-01T00:00:00Z' }))
38
41
  expectError(tokens.create({ name: 'CI', expiresIn: '7d' }))
39
42
  expectError(tokens.updateById('t-1', { scope: '*' }))
43
+
44
+ // ── users: the dedicated archive route takes a boolean, not a date ──
45
+
46
+ const users = new UsersApi(client)
47
+ users.archiveWithValidation('u-1', { archive: true })
48
+ expectError(users.archiveWithValidation('u-1', { archivedAt: '2030-01-01T00:00:00Z' }))
49
+ expectError(users.exportCsv({ filter: 'deleted' }))
50
+
51
+ // ── contacts: /many takes an array, the bulk delete a selection ─────
52
+
53
+ const contacts = new ContactsApi(client)
54
+ contacts.createMany([{ serviceId: 's-1', name: 'Alice', number: '5511999990001' }])
55
+ expectError(contacts.createMany({ serviceId: 's-1', name: 'Alice', number: '5511999990001' }))
56
+ expectError(contacts.getMedia('c-1', { where: { type: 'audio' } }))
57
+
58
+ // ── contact selections: an empty filter would reach every contact of the account ─
59
+
60
+ contacts.deleteMany({ where: { id: 'c-1' } })
61
+ expectError(contacts.deleteMany({}))
62
+ const tags = new TagsApi(client)
63
+ tags.attachContacts('t-1', { where: { id: { $in: ['c-1', 'c-2'] } } })
64
+ expectError(tags.attachContacts('t-1', {}))
65
+ expectError(tags.detachContacts('t-1', { include: ['tags'] }))
@@ -32,6 +32,8 @@ function createMockClient(queryFormat?: QueryFormat): ApiClient {
32
32
 
33
33
  const listQuery = { where: { name: 'x' }, page: 2, perPage: 5 }
34
34
  const getQuery = { attributes: ['id'] }
35
+ /** `Permission.name` is a union of the catalog names, so the permissions case filters by a real one. */
36
+ const permissionsQuery = { where: { name: 'chat' as const }, page: 2, perPage: 5 }
35
37
 
36
38
  type Case = {
37
39
  name: string
@@ -69,6 +71,27 @@ const cases: Case[] = [
69
71
  return (f ? api.withQueryFormat(f) : api).count(listQuery)
70
72
  },
71
73
  },
74
+ {
75
+ name: 'ContactsApi.getMedia',
76
+ path: '/contacts/c1/media',
77
+ query: { where: { type: 'document' }, perPage: 10 },
78
+ call: (client, f) => {
79
+ const api = new ContactsApi(client)
80
+ return (f ? api.withQueryFormat(f) : api).getMedia('c1', {
81
+ where: { type: 'document' },
82
+ perPage: 10,
83
+ })
84
+ },
85
+ },
86
+ {
87
+ name: 'ContactsApi.createMany',
88
+ path: '/contacts/many',
89
+ query: { include: ['tags'] },
90
+ call: (client, f) => {
91
+ const api = new ContactsApi(client)
92
+ return (f ? api.withQueryFormat(f) : api).createMany([], { include: ['tags'] })
93
+ },
94
+ },
72
95
  {
73
96
  name: 'ContactsApi.forward',
74
97
  path: '/contacts/forward',
@@ -99,6 +122,70 @@ const cases: Case[] = [
99
122
  })
100
123
  },
101
124
  },
125
+ {
126
+ name: 'DashboardApi.getByPeriod',
127
+ path: '/dashboard/by-period',
128
+ query: { status: 'closed', departmentId: ['d1'], grouping: 'weekly', withTotals: true },
129
+ call: (client, f) => {
130
+ const api = new DashboardApi(client)
131
+ return (f ? api.withQueryFormat(f) : api).getByPeriod({
132
+ status: 'closed',
133
+ departmentId: ['d1'],
134
+ grouping: 'weekly',
135
+ withTotals: true,
136
+ })
137
+ },
138
+ },
139
+ {
140
+ name: 'DashboardApi.getByUser',
141
+ path: '/dashboard/by-user',
142
+ query: { status: 'closed', departmentId: ['d1'], withTotals: true },
143
+ call: (client, f) => {
144
+ const api = new DashboardApi(client)
145
+ return (f ? api.withQueryFormat(f) : api).getByUser({
146
+ status: 'closed',
147
+ departmentId: ['d1'],
148
+ withTotals: true,
149
+ })
150
+ },
151
+ },
152
+ {
153
+ name: 'DashboardApi.getByDepartment',
154
+ path: '/dashboard/by-department',
155
+ query: { status: 'closed', departmentId: ['d1'], withTotals: true },
156
+ call: (client, f) => {
157
+ const api = new DashboardApi(client)
158
+ return (f ? api.withQueryFormat(f) : api).getByDepartment({
159
+ status: 'closed',
160
+ departmentId: ['d1'],
161
+ withTotals: true,
162
+ })
163
+ },
164
+ },
165
+ {
166
+ name: 'DashboardApi.getByTopic',
167
+ path: '/dashboard/by-topic',
168
+ query: { status: 'closed', departmentId: ['d1'] },
169
+ call: (client, f) => {
170
+ const api = new DashboardApi(client)
171
+ return (f ? api.withQueryFormat(f) : api).getByTopic({
172
+ status: 'closed',
173
+ departmentId: ['d1'],
174
+ })
175
+ },
176
+ },
177
+ {
178
+ name: 'DashboardApi.getByService',
179
+ path: '/dashboard/by-service',
180
+ query: { status: 'closed', departmentId: ['d1'] },
181
+ call: (client, f) => {
182
+ const api = new DashboardApi(client)
183
+ return (f ? api.withQueryFormat(f) : api).getByService({
184
+ status: 'closed',
185
+ departmentId: ['d1'],
186
+ })
187
+ },
188
+ },
102
189
  {
103
190
  name: 'NowApi.getResume',
104
191
  path: '/now/resume',
@@ -135,10 +222,10 @@ const cases: Case[] = [
135
222
  {
136
223
  name: 'PermissionsApi.getMany',
137
224
  path: '/permissions',
138
- query: listQuery,
225
+ query: permissionsQuery,
139
226
  call: (client, f) => {
140
227
  const api = new PermissionsApi(client)
141
- return (f ? api.withQueryFormat(f) : api).getMany(listQuery)
228
+ return (f ? api.withQueryFormat(f) : api).getMany(permissionsQuery)
142
229
  },
143
230
  },
144
231
  {
@@ -191,7 +278,8 @@ const cases: Case[] = [
191
278
  const jsonUrl = (path: string, query: object) =>
192
279
  `${path}?query=${encodeURIComponent(JSON.stringify(query))}`
193
280
  const qsUrl = (path: string, query: object) => `${path}?${qs.stringify(query)}`
194
- const calledUrl = (client: ApiClient) => vi.mocked(client.get).mock.calls.at(0)?.at(0)
281
+ const calledUrl = (client: ApiClient) =>
282
+ vi.mocked(client.get).mock.calls.at(0)?.at(0) ?? vi.mocked(client.post).mock.calls.at(0)?.at(0)
195
283
 
196
284
  describe.each(cases)('$name', ({ path, query, call }) => {
197
285
  it("sends JSON with the default client format ('json')", async () => {
@@ -61,7 +61,7 @@ describe('ServiceAccessManagementApi', () => {
61
61
  it('serializes query parameters', async () => {
62
62
  vi.mocked(client.get).mockResolvedValue(paginated)
63
63
  await api.getMany({ page: 2, perPage: 5 })
64
- const url = vi.mocked(client.get).mock.calls[0][0] as string
64
+ const url = vi.mocked(client.get).mock.calls[0]?.[0] as string
65
65
  expect(url).toContain('/service-access-management?')
66
66
  expect(decodeURIComponent(url)).toContain('"page":2')
67
67
  })
@@ -76,7 +76,7 @@ describe('ServiceAccessManagementApi', () => {
76
76
  it('returns unpaginated array when paginate: false', async () => {
77
77
  vi.mocked(client.get).mockResolvedValue([record])
78
78
  const result = await api.getMany({ paginate: false })
79
- const url = vi.mocked(client.get).mock.calls[0][0] as string
79
+ const url = vi.mocked(client.get).mock.calls[0]?.[0] as string
80
80
  expect(decodeURIComponent(url)).toContain('"paginate":false')
81
81
  expect(result).toEqual([record])
82
82
  })
@@ -0,0 +1,49 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import { ServicesApi } from './ServicesApi'
3
+ import type { ApiClient } from '../../core/ApiClient'
4
+ import type { ServiceLimits } 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
+ // ── Tests ────────────────────────────────────────────────────────────
21
+
22
+ describe('ServicesApi', () => {
23
+ let client: ReturnType<typeof createMockClient>
24
+ let api: ServicesApi
25
+
26
+ beforeEach(() => {
27
+ client = createMockClient()
28
+ api = new ServicesApi(client)
29
+ })
30
+
31
+ describe('getLimits', () => {
32
+ it('calls client.get with /services/limits', async () => {
33
+ const limits: ServiceLimits = {
34
+ whatsapp: { used: 1, total: 80, available: 79 },
35
+ telegram: { used: 0, total: 0, available: 0 },
36
+ }
37
+ vi.mocked(client.get).mockResolvedValue(limits)
38
+ const result = await api.getLimits()
39
+ expect(client.get).toHaveBeenCalledWith('/services/limits', undefined)
40
+ expect(result).toEqual(limits)
41
+ })
42
+
43
+ it('forwards custom headers', async () => {
44
+ const headers = { 'X-Custom': 'value' }
45
+ await api.getLimits(headers)
46
+ expect(client.get).toHaveBeenCalledWith('/services/limits', headers)
47
+ })
48
+ })
49
+ })
@@ -1,6 +1,6 @@
1
1
  import type { ApiClient } from '../../core/ApiClient'
2
2
  import { ArchivableCrudApi } from '../../core/ArchivableCrudApi'
3
- import type { Service, CreateServicePayload, UpdateServicePayload } from './types'
3
+ import type { Service, CreateServicePayload, ServiceLimits, UpdateServicePayload } from './types'
4
4
 
5
5
  export class ServicesApi extends ArchivableCrudApi<
6
6
  Service,
@@ -10,4 +10,13 @@ export class ServicesApi extends ArchivableCrudApi<
10
10
  constructor(client: ApiClient) {
11
11
  super(client, '/services')
12
12
  }
13
+
14
+ /**
15
+ * Returns, for each connection type of the account plan, how many connections are used,
16
+ * allowed and still available.
17
+ * @permissions services.view
18
+ */
19
+ getLimits(headers?: Record<string, string>): Promise<ServiceLimits> {
20
+ return this.client.get<ServiceLimits>(`${this.basePath}/limits`, headers)
21
+ }
13
22
  }
@@ -221,3 +221,12 @@ export type UpdateServicePayload = {
221
221
  botId?: string | null
222
222
  archivedAt?: string | null
223
223
  }
224
+
225
+ export type ServiceLimit = {
226
+ used: number
227
+ total: number
228
+ available: number
229
+ }
230
+
231
+ /** Connections used and allowed by the account plan, by connection type. */
232
+ export type ServiceLimits = Partial<Record<ServiceType, ServiceLimit>>
@@ -64,7 +64,7 @@ describe('StickerUsersApi', () => {
64
64
  it('serializes query parameters', async () => {
65
65
  vi.mocked(client.get).mockResolvedValue(paginated)
66
66
  await api.getMany({ page: 2, perPage: 5 })
67
- const url = vi.mocked(client.get).mock.calls[0][0] as string
67
+ const url = vi.mocked(client.get).mock.calls[0]?.[0] as string
68
68
  expect(url).toContain('/sticker-users?')
69
69
  expect(decodeURIComponent(url)).toContain('"page":2')
70
70
  })
@@ -0,0 +1,79 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import { TagsApi } from './TagsApi'
3
+ import type { ApiClient } from '../../core/ApiClient'
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
+
21
+ // ── Tests ────────────────────────────────────────────────────────────
22
+
23
+ describe('TagsApi', () => {
24
+ let client: ReturnType<typeof createMockClient>
25
+ let api: TagsApi
26
+
27
+ beforeEach(() => {
28
+ client = createMockClient()
29
+ api = new TagsApi(client)
30
+ })
31
+
32
+ describe('attachContacts', () => {
33
+ it('calls client.post with /tags/:id/contacts and the selection', async () => {
34
+ vi.mocked(client.post).mockResolvedValue('OK')
35
+ const body = { where: { id: { $in: ['c1', 'c2'] } } }
36
+ const result = await api.attachContacts('t1', body)
37
+ expect(client.post).toHaveBeenCalledWith('/tags/t1/contacts', body, undefined)
38
+ expect(result).toBe('OK')
39
+ })
40
+
41
+ it('forwards custom headers', async () => {
42
+ await api.attachContacts('t1', { where: { id: 'c1' } }, headers)
43
+ expect(client.post).toHaveBeenCalledWith(
44
+ '/tags/t1/contacts',
45
+ { where: { id: 'c1' } },
46
+ headers,
47
+ )
48
+ })
49
+ })
50
+
51
+ describe('detachContacts', () => {
52
+ it('calls client.request with DELETE /tags/:id/contacts and the selection', async () => {
53
+ vi.mocked(client.request).mockResolvedValue('OK')
54
+ const body = { where: { id: 'c1' } }
55
+ const result = await api.detachContacts('t1', body, headers)
56
+ expect(client.request).toHaveBeenCalledWith('DELETE', '/tags/t1/contacts', body, headers)
57
+ expect(result).toBe('OK')
58
+ })
59
+ })
60
+
61
+ describe('attachManyToDepartments', () => {
62
+ it('calls client.post with /tags/attach-many-departments and body', async () => {
63
+ vi.mocked(client.post).mockResolvedValue('OK')
64
+ const body = { ids: ['t1', 't2'], departmentIds: ['d1'] }
65
+ const result = await api.attachManyToDepartments(body)
66
+ expect(client.post).toHaveBeenCalledWith('/tags/attach-many-departments', body, undefined)
67
+ expect(result).toBe('OK')
68
+ })
69
+ })
70
+
71
+ describe('error propagation', () => {
72
+ it('propagates errors from attachManyToDepartments', async () => {
73
+ vi.mocked(client.post).mockRejectedValue(new Error('Invalid tag ids'))
74
+ await expect(api.attachManyToDepartments({ ids: [], departmentIds: [] })).rejects.toThrow(
75
+ 'Invalid tag ids',
76
+ )
77
+ })
78
+ })
79
+ })
@@ -1,9 +1,65 @@
1
1
  import type { ApiClient } from '../../core/ApiClient'
2
2
  import { BaseCrudApi } from '../../core/BaseCrudApi'
3
- import type { Tag, CreateTagPayload, UpdateTagPayload } from './types'
3
+ import type { ContactsSelection } from '../contacts/types'
4
+ import type {
5
+ AttachTagsToDepartmentsPayload,
6
+ CreateTagPayload,
7
+ Tag,
8
+ UpdateTagPayload,
9
+ } from './types'
4
10
 
5
11
  export class TagsApi extends BaseCrudApi<Tag, CreateTagPayload, UpdateTagPayload> {
6
12
  constructor(client: ApiClient) {
7
13
  super(client, '/tags')
8
14
  }
15
+
16
+ /**
17
+ * Adds the tag to every contact of the account that matches the selection. Resolves to `'OK'`.
18
+ * @permissions tags.view
19
+ */
20
+ attachContacts(
21
+ id: string,
22
+ body: ContactsSelection,
23
+ headers?: Record<string, string>,
24
+ ): Promise<string> {
25
+ return this.client.post<string, ContactsSelection>(
26
+ `${this.basePath}/${id}/contacts`,
27
+ body,
28
+ headers,
29
+ )
30
+ }
31
+
32
+ /**
33
+ * Removes the tag from every contact of the account that matches the selection. Resolves to
34
+ * `'OK'`.
35
+ * @permissions tags.view OR tags.destroy
36
+ */
37
+ detachContacts(
38
+ id: string,
39
+ body: ContactsSelection,
40
+ headers?: Record<string, string>,
41
+ ): Promise<string> {
42
+ return this.client.request<string, ContactsSelection>(
43
+ 'DELETE',
44
+ `${this.basePath}/${id}/contacts`,
45
+ body,
46
+ headers,
47
+ )
48
+ }
49
+
50
+ /**
51
+ * Links several tags to the given departments. Resolves to `'OK'`. A tag without departments
52
+ * is already available to all of them and is left unchanged.
53
+ * @permissions tags.view
54
+ */
55
+ attachManyToDepartments(
56
+ body: AttachTagsToDepartmentsPayload,
57
+ headers?: Record<string, string>,
58
+ ): Promise<string> {
59
+ return this.client.post<string, AttachTagsToDepartmentsPayload>(
60
+ `${this.basePath}/attach-many-departments`,
61
+ body,
62
+ headers,
63
+ )
64
+ }
9
65
  }
@@ -27,3 +27,12 @@ export type UpdateTagPayload = {
27
27
  label?: string
28
28
  backgroundColor?: string | null
29
29
  }
30
+
31
+ /**
32
+ * Body for `POST /tags/attach-many-departments`. An empty `departmentIds` makes the tags
33
+ * available to every department.
34
+ */
35
+ export type AttachTagsToDepartmentsPayload = {
36
+ ids: string[]
37
+ departmentIds: string[]
38
+ }
@@ -66,7 +66,7 @@ describe('TermsApi', () => {
66
66
  it('serializes the query', async () => {
67
67
  vi.mocked(client.get).mockResolvedValue(term)
68
68
  await api.getById('term-1', { attributes: ['id', 'version'] })
69
- const url = vi.mocked(client.get).mock.calls[0][0] as string
69
+ const url = vi.mocked(client.get).mock.calls[0]?.[0] as string
70
70
  expect(url).toContain('/terms/term-1?')
71
71
  expect(url).toContain('attributes')
72
72
  })