@ikatec/digisac-api-sdk 4.12.0 → 4.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/apis/absence/AbsenceApi.cjs +46 -2
- package/dist/apis/absence/AbsenceApi.cjs.map +1 -1
- package/dist/apis/absence/AbsenceApi.d.ts +38 -4
- package/dist/apis/absence/AbsenceApi.d.ts.map +1 -1
- package/dist/apis/absence/AbsenceApi.mjs +46 -2
- package/dist/apis/absence/AbsenceApi.mjs.map +1 -1
- package/dist/apis/absence/types.d.ts +51 -2
- package/dist/apis/absence/types.d.ts.map +1 -1
- package/dist/apis/activityLog/ActivityLogApi.cjs +10 -1
- package/dist/apis/activityLog/ActivityLogApi.cjs.map +1 -1
- package/dist/apis/activityLog/ActivityLogApi.d.ts +10 -3
- package/dist/apis/activityLog/ActivityLogApi.d.ts.map +1 -1
- package/dist/apis/activityLog/ActivityLogApi.mjs +10 -1
- package/dist/apis/activityLog/ActivityLogApi.mjs.map +1 -1
- package/dist/apis/activityLog/types.d.ts +48 -8
- package/dist/apis/activityLog/types.d.ts.map +1 -1
- package/dist/apis/creditMovements/CreditMovementsApi.cjs +31 -2
- package/dist/apis/creditMovements/CreditMovementsApi.cjs.map +1 -1
- package/dist/apis/creditMovements/CreditMovementsApi.d.ts +21 -3
- package/dist/apis/creditMovements/CreditMovementsApi.d.ts.map +1 -1
- package/dist/apis/creditMovements/CreditMovementsApi.mjs +31 -2
- package/dist/apis/creditMovements/CreditMovementsApi.mjs.map +1 -1
- package/dist/apis/creditMovements/types.d.ts +73 -2
- package/dist/apis/creditMovements/types.d.ts.map +1 -1
- package/dist/apis/knowledgeBase/KnowledgeBaseApi.cjs +27 -0
- package/dist/apis/knowledgeBase/KnowledgeBaseApi.cjs.map +1 -1
- package/dist/apis/knowledgeBase/KnowledgeBaseApi.d.ts +22 -1
- package/dist/apis/knowledgeBase/KnowledgeBaseApi.d.ts.map +1 -1
- package/dist/apis/knowledgeBase/KnowledgeBaseApi.mjs +27 -0
- package/dist/apis/knowledgeBase/KnowledgeBaseApi.mjs.map +1 -1
- package/dist/apis/knowledgeBase/types.d.ts +56 -2
- package/dist/apis/knowledgeBase/types.d.ts.map +1 -1
- package/dist/apis/knowledgeBaseItem/KnowledgeBaseItemApi.cjs +26 -0
- package/dist/apis/knowledgeBaseItem/KnowledgeBaseItemApi.cjs.map +1 -1
- package/dist/apis/knowledgeBaseItem/KnowledgeBaseItemApi.d.ts +24 -0
- package/dist/apis/knowledgeBaseItem/KnowledgeBaseItemApi.d.ts.map +1 -1
- package/dist/apis/knowledgeBaseItem/KnowledgeBaseItemApi.mjs +26 -0
- package/dist/apis/knowledgeBaseItem/KnowledgeBaseItemApi.mjs.map +1 -1
- package/dist/apis/knowledgeBaseItem/types.d.ts +23 -6
- package/dist/apis/knowledgeBaseItem/types.d.ts.map +1 -1
- package/dist/apis/notifications/NotificationsApi.cjs +50 -2
- package/dist/apis/notifications/NotificationsApi.cjs.map +1 -1
- package/dist/apis/notifications/NotificationsApi.d.ts +41 -3
- package/dist/apis/notifications/NotificationsApi.d.ts.map +1 -1
- package/dist/apis/notifications/NotificationsApi.mjs +50 -2
- package/dist/apis/notifications/NotificationsApi.mjs.map +1 -1
- package/dist/apis/notifications/types.d.ts +57 -8
- package/dist/apis/notifications/types.d.ts.map +1 -1
- package/dist/apis/serviceAccessManagement/ServiceAccessManagementApi.cjs.map +1 -1
- package/dist/apis/serviceAccessManagement/ServiceAccessManagementApi.d.ts +4 -1
- package/dist/apis/serviceAccessManagement/ServiceAccessManagementApi.d.ts.map +1 -1
- package/dist/apis/serviceAccessManagement/ServiceAccessManagementApi.mjs.map +1 -1
- package/dist/apis/serviceAccessManagement/types.d.ts +3 -0
- package/dist/apis/serviceAccessManagement/types.d.ts.map +1 -1
- package/dist/apis/services/ServicesApi.cjs +10 -0
- package/dist/apis/services/ServicesApi.cjs.map +1 -1
- package/dist/apis/services/ServicesApi.d.ts +9 -1
- package/dist/apis/services/ServicesApi.d.ts.map +1 -1
- package/dist/apis/services/ServicesApi.mjs +10 -0
- package/dist/apis/services/ServicesApi.mjs.map +1 -1
- package/dist/apis/services/types.d.ts +10 -0
- package/dist/apis/services/types.d.ts.map +1 -1
- package/dist/apis/stickerUsers/StickerUsersApi.cjs.map +1 -1
- package/dist/apis/stickerUsers/StickerUsersApi.d.ts +3 -1
- package/dist/apis/stickerUsers/StickerUsersApi.d.ts.map +1 -1
- package/dist/apis/stickerUsers/StickerUsersApi.mjs.map +1 -1
- package/dist/apis/stickerUsers/types.d.ts +4 -0
- package/dist/apis/stickerUsers/types.d.ts.map +1 -1
- package/dist/apis/stickers/StickersApi.cjs +27 -3
- package/dist/apis/stickers/StickersApi.cjs.map +1 -1
- package/dist/apis/stickers/StickersApi.d.ts +24 -4
- package/dist/apis/stickers/StickersApi.d.ts.map +1 -1
- package/dist/apis/stickers/StickersApi.mjs +27 -3
- package/dist/apis/stickers/StickersApi.mjs.map +1 -1
- package/dist/apis/stickers/types.d.ts +31 -4
- package/dist/apis/stickers/types.d.ts.map +1 -1
- package/dist/apis/whatsappBusinessTemplates/WhatsappBusinessTemplatesApi.cjs +1 -2
- package/dist/apis/whatsappBusinessTemplates/WhatsappBusinessTemplatesApi.cjs.map +1 -1
- package/dist/apis/whatsappBusinessTemplates/WhatsappBusinessTemplatesApi.d.ts +1 -2
- package/dist/apis/whatsappBusinessTemplates/WhatsappBusinessTemplatesApi.d.ts.map +1 -1
- package/dist/apis/whatsappBusinessTemplates/WhatsappBusinessTemplatesApi.mjs +1 -2
- package/dist/apis/whatsappBusinessTemplates/WhatsappBusinessTemplatesApi.mjs.map +1 -1
- package/package.json +1 -1
- package/src/apis/absence/AbsenceApi.test.ts +65 -0
- package/src/apis/absence/AbsenceApi.ts +77 -4
- package/src/apis/absence/types.ts +53 -2
- package/src/apis/activityLog/ActivityLogApi.test.ts +44 -0
- package/src/apis/activityLog/ActivityLogApi.ts +22 -3
- package/src/apis/activityLog/types.ts +47 -8
- package/src/apis/creditMovements/CreditMovementsApi.test.ts +88 -0
- package/src/apis/creditMovements/CreditMovementsApi.ts +73 -2
- package/src/apis/creditMovements/types.ts +74 -2
- package/src/apis/knowledgeBase/KnowledgeBaseApi.test.ts +54 -0
- package/src/apis/knowledgeBase/KnowledgeBaseApi.ts +61 -1
- package/src/apis/knowledgeBase/types.ts +62 -2
- package/src/apis/knowledgeBaseItem/KnowledgeBaseItemApi.test.ts +57 -0
- package/src/apis/knowledgeBaseItem/KnowledgeBaseItemApi.ts +45 -0
- package/src/apis/knowledgeBaseItem/types.ts +23 -6
- package/src/apis/me/MeApi.test.ts +1 -1
- package/src/apis/notifications/NotificationsApi.test.ts +74 -0
- package/src/apis/notifications/NotificationsApi.ts +88 -3
- package/src/apis/notifications/types.ts +68 -7
- package/src/apis/payloads.test-d.ts +66 -0
- package/src/apis/serviceAccessManagement/ServiceAccessManagementApi.ts +4 -1
- package/src/apis/serviceAccessManagement/types.ts +3 -0
- package/src/apis/services/ServicesApi.test.ts +11 -0
- package/src/apis/services/ServicesApi.ts +21 -0
- package/src/apis/services/types.ts +12 -0
- package/src/apis/stickerUsers/StickerUsersApi.ts +3 -1
- package/src/apis/stickerUsers/types.ts +4 -0
- package/src/apis/stickers/StickersApi.test.ts +58 -0
- package/src/apis/stickers/StickersApi.ts +46 -4
- package/src/apis/stickers/types.ts +32 -4
- package/src/apis/whatsappBusinessTemplates/WhatsappBusinessTemplatesApi.ts +1 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { User } from '../users'
|
|
2
2
|
|
|
3
|
+
/** An absence as `PUT /absence/:id` answers it (the transformer of the backend). */
|
|
3
4
|
export type Absence = {
|
|
4
5
|
id: string
|
|
5
6
|
accountId: string
|
|
@@ -8,7 +9,8 @@ export type Absence = {
|
|
|
8
9
|
endedAt: string | null
|
|
9
10
|
createdAt: string
|
|
10
11
|
updatedAt: string
|
|
11
|
-
|
|
12
|
+
/** @deprecated Not a field of the backend: the model has no soft delete. */
|
|
13
|
+
deletedAt?: string | null
|
|
12
14
|
// relationships
|
|
13
15
|
user?: User
|
|
14
16
|
}
|
|
@@ -16,7 +18,9 @@ export type Absence = {
|
|
|
16
18
|
export type AbsenceRelationships = 'user'
|
|
17
19
|
|
|
18
20
|
export type CreateAbsencePayload = {
|
|
19
|
-
|
|
21
|
+
/** Ignored: the backend creates the absence for the current user. */
|
|
22
|
+
userId?: string
|
|
23
|
+
/** Up to 255 characters. */
|
|
20
24
|
reason?: string
|
|
21
25
|
}
|
|
22
26
|
|
|
@@ -24,3 +28,50 @@ export type UpdateAbsencePayload = {
|
|
|
24
28
|
reason?: string
|
|
25
29
|
endedAt?: string | null
|
|
26
30
|
}
|
|
31
|
+
|
|
32
|
+
/** Columns the report can be sorted by (an unknown one falls back to the default order). */
|
|
33
|
+
export type AbsenceReportSortField = 'user' | 'createdAt' | 'endedAt'
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Query of the absence report (`GET /absence` and `GET /absence/export-csv`). The backend ignores
|
|
37
|
+
* `where` and `include`: it reads only these fields.
|
|
38
|
+
*/
|
|
39
|
+
export type AbsenceReportQuery = {
|
|
40
|
+
filters?: {
|
|
41
|
+
userId?: string
|
|
42
|
+
reason?: string
|
|
43
|
+
/** Absences created at or after this date. An invalid date answers 400 `Invalid date.`. */
|
|
44
|
+
createdAt?: string
|
|
45
|
+
/** Absences ended at or before this date. */
|
|
46
|
+
endedAt?: string
|
|
47
|
+
}
|
|
48
|
+
sort?: AbsenceReportSortField
|
|
49
|
+
/** Only `'ASC'` sorts ascending: any other value sorts descending. */
|
|
50
|
+
order?: 'ASC' | 'DESC'
|
|
51
|
+
page?: number
|
|
52
|
+
/** Up to 200 (a larger value is clamped, not refused). Defaults to 15. */
|
|
53
|
+
perPage?: number
|
|
54
|
+
/** IANA zone of the dates of the CSV export (e.g. `America/Sao_Paulo`). */
|
|
55
|
+
timezone?: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A row of the report: the stored absence with its user, both raw (not transformed).
|
|
60
|
+
*/
|
|
61
|
+
export type AbsenceReportItem = Omit<Absence, 'deletedAt' | 'user'> & {
|
|
62
|
+
user?: Record<string, unknown>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The body of `unlock`: the absence to end (the open one of the user when `id` is absent). */
|
|
66
|
+
export type UnlockAbsencePayload = {
|
|
67
|
+
id?: string
|
|
68
|
+
/** Required when `settings.absence.reason_required` is on. */
|
|
69
|
+
reason?: string
|
|
70
|
+
/** Required when `settings.absence.password_required` is on: the password of the user. */
|
|
71
|
+
password?: string
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** What `unlock` answers: the business refusals come with status 200. */
|
|
75
|
+
export type UnlockAbsenceResponse =
|
|
76
|
+
| { success: true }
|
|
77
|
+
| { success: false; message: 'INVALID_REASON' | 'INVALID_PASSWORD' }
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { ActivityLogApi } from './ActivityLogApi'
|
|
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
|
+
requestRaw: 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('ActivityLogApi', () => {
|
|
23
|
+
let client: ReturnType<typeof createMockClient>
|
|
24
|
+
let api: ActivityLogApi
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
client = createMockClient()
|
|
28
|
+
api = new ActivityLogApi(client)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('exportCsv calls client.get with /log/export-csv and the period', async () => {
|
|
32
|
+
vi.mocked(client.get).mockResolvedValue('csv')
|
|
33
|
+
await api.exportCsv({ filters: { from: '2026-10-01', to: '2026-10-17' } })
|
|
34
|
+
const url = vi.mocked(client.get).mock.calls[0]?.[0] as string
|
|
35
|
+
expect(url).toMatch(/^\/log\/export-csv\?/)
|
|
36
|
+
expect(decodeURIComponent(url)).toContain('"from":"2026-10-01"')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('create posts the stored fields of the log', async () => {
|
|
40
|
+
const body = { resourceType: 'contact', resourceId: 'c-1', event: 'update', data: { a: 1 } }
|
|
41
|
+
await api.create(body)
|
|
42
|
+
expect(client.post).toHaveBeenCalledWith('/log', body, undefined)
|
|
43
|
+
})
|
|
44
|
+
})
|
|
@@ -1,20 +1,39 @@
|
|
|
1
1
|
import type { ApiClient } from '../../core/ApiClient'
|
|
2
2
|
import { BaseCrudApi } from '../../core/BaseCrudApi'
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
ActivityLog,
|
|
5
|
+
ActivityLogListQuery,
|
|
6
|
+
CreateActivityLogPayload,
|
|
7
|
+
UpdateActivityLogPayload,
|
|
8
|
+
} from './types'
|
|
4
9
|
|
|
5
10
|
/**
|
|
6
11
|
* Activity log of the account (stored in MongoDB: the routes answer 503 when it is unavailable).
|
|
7
|
-
* The backend serves `GET /log`, `GET /log/:id` and `POST /log`; there is no update
|
|
12
|
+
* The backend serves `GET /log`, `GET /log/export-csv`, `GET /log/:id` and `POST /log`; there is no update
|
|
13
|
+
* or delete, so an entry created is permanent. `GET /log/:id` takes a MongoDB
|
|
14
|
+
* id (an UUID answers 500).
|
|
8
15
|
*/
|
|
9
16
|
export class ActivityLogApi extends BaseCrudApi<
|
|
10
17
|
ActivityLog,
|
|
11
18
|
CreateActivityLogPayload,
|
|
12
|
-
UpdateActivityLogPayload
|
|
19
|
+
UpdateActivityLogPayload,
|
|
20
|
+
ActivityLogListQuery
|
|
13
21
|
> {
|
|
14
22
|
constructor(client: ApiClient) {
|
|
15
23
|
super(client, '/log')
|
|
16
24
|
}
|
|
17
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Exports the entries of the period (`filters.from`/`filters.to`) as CSV, sent as the attachment
|
|
28
|
+
* `exportation.csv`.
|
|
29
|
+
*/
|
|
30
|
+
exportCsv(query: ActivityLogListQuery, headers?: Record<string, string>): Promise<string> {
|
|
31
|
+
return this.client.get<string>(
|
|
32
|
+
`${this.basePath}/export-csv${this.toQueryString(query)}`,
|
|
33
|
+
headers,
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
18
37
|
/** @deprecated Not served: the backend has no PUT /log/:id, so this route answers 404. */
|
|
19
38
|
override updateById(
|
|
20
39
|
id: string,
|
|
@@ -1,22 +1,61 @@
|
|
|
1
|
+
import type { ListQuery } from '../../core/types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* An entry of the activity log (MongoDB). The stored fields are `resourceType`, `resourceId`, `event`,
|
|
5
|
+
* `userId`, `accessTokenId` and `data`. `create` and `getById` answer through a transformer that keeps only
|
|
6
|
+
* `id`, `accountId`, `createdAt` and `updatedAt`; the list answers every stored field plus `user`.
|
|
7
|
+
*/
|
|
1
8
|
export type ActivityLog = {
|
|
2
9
|
id: string
|
|
3
10
|
accountId: string
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
11
|
+
resourceType?: string
|
|
12
|
+
resourceId?: string | null
|
|
13
|
+
event?: string
|
|
14
|
+
userId?: string | null
|
|
15
|
+
accessTokenId?: string | null
|
|
16
|
+
data?: Record<string, unknown>
|
|
9
17
|
createdAt: string
|
|
10
18
|
updatedAt: string
|
|
19
|
+
/** On the list: the user of the entry. */
|
|
20
|
+
user?: { id: string; name: string }
|
|
21
|
+
/** @deprecated Not a field of the backend (it is dropped on create): use `event`. */
|
|
22
|
+
action?: string
|
|
23
|
+
/** @deprecated Not a field of the backend (it is dropped on create): use `resourceType`. */
|
|
24
|
+
entity?: string
|
|
25
|
+
/** @deprecated Not a field of the backend (it is dropped on create): use `resourceId`. */
|
|
26
|
+
entityId?: string | null
|
|
11
27
|
}
|
|
12
28
|
|
|
13
29
|
export type CreateActivityLogPayload = {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
30
|
+
resourceType?: string
|
|
31
|
+
resourceId?: string
|
|
32
|
+
event?: string
|
|
33
|
+
userId?: string
|
|
34
|
+
accessTokenId?: string
|
|
17
35
|
data?: Record<string, unknown>
|
|
36
|
+
/** @deprecated Dropped by the backend (not a field of the log): use `event`. */
|
|
37
|
+
action?: string
|
|
38
|
+
/** @deprecated Dropped by the backend (not a field of the log): use `resourceType`. */
|
|
39
|
+
entity?: string
|
|
40
|
+
/** @deprecated Dropped by the backend (not a field of the log): use `resourceId`. */
|
|
41
|
+
entityId?: string
|
|
18
42
|
}
|
|
19
43
|
|
|
20
44
|
export type UpdateActivityLogPayload = {
|
|
21
45
|
data?: Record<string, unknown>
|
|
22
46
|
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Query of the list and of the CSV export. The backend requires `filters.from` and `filters.to` (the
|
|
50
|
+
* period of `createdAt`); the other `filters` match the stored fields.
|
|
51
|
+
*/
|
|
52
|
+
export type ActivityLogListQuery = ListQuery<ActivityLog> & {
|
|
53
|
+
filters?: {
|
|
54
|
+
from?: string
|
|
55
|
+
to?: string
|
|
56
|
+
resourceType?: string
|
|
57
|
+
resourceId?: string
|
|
58
|
+
event?: string
|
|
59
|
+
userId?: string
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { CreditMovementsApi } from './CreditMovementsApi'
|
|
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
|
+
requestRaw: 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('CreditMovementsApi', () => {
|
|
23
|
+
let client: ReturnType<typeof createMockClient>
|
|
24
|
+
let api: CreditMovementsApi
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
client = createMockClient()
|
|
28
|
+
api = new CreditMovementsApi(client)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('balances calls client.get with /credit-movements/balances', async () => {
|
|
32
|
+
await api.balances()
|
|
33
|
+
expect(client.get).toHaveBeenCalledWith('/credit-movements/balances', undefined)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('balance posts the service type to /credit-movements/balance', async () => {
|
|
37
|
+
vi.mocked(client.post).mockResolvedValue(20)
|
|
38
|
+
await expect(api.balance('sms-wavy')).resolves.toBe(20)
|
|
39
|
+
expect(client.post).toHaveBeenCalledWith(
|
|
40
|
+
'/credit-movements/balance',
|
|
41
|
+
{ serviceType: 'sms-wavy' },
|
|
42
|
+
undefined,
|
|
43
|
+
)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('balancesV2 serializes the period', async () => {
|
|
47
|
+
await api.balancesV2({ from: '2026-10-01', to: '2026-10-17' })
|
|
48
|
+
const url = vi.mocked(client.get).mock.calls[0]?.[0] as string
|
|
49
|
+
expect(url).toMatch(/^\/credit-movements\/v2\/balances\?/)
|
|
50
|
+
expect(decodeURIComponent(url)).toContain('"from":"2026-10-01"')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('balancesV2 sends no query without a period', async () => {
|
|
54
|
+
await api.balancesV2()
|
|
55
|
+
expect(client.get).toHaveBeenCalledWith('/credit-movements/v2/balances', undefined)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('totalServices serializes the filters and the page', async () => {
|
|
59
|
+
await api.totalServices({
|
|
60
|
+
filters: { from: '2026-10-01', to: '2026-10-17', serviceType: 'summary' },
|
|
61
|
+
page: 1,
|
|
62
|
+
})
|
|
63
|
+
const url = vi.mocked(client.get).mock.calls[0]?.[0] as string
|
|
64
|
+
expect(url).toMatch(/^\/credit-movements\/total-services\?/)
|
|
65
|
+
expect(decodeURIComponent(url)).toContain('"page":1')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('exportXlsx resolves the binary body of requestRaw', async () => {
|
|
69
|
+
const requestRaw = vi.fn().mockResolvedValue(new Response(new ArrayBuffer(4)))
|
|
70
|
+
client.requestRaw = requestRaw
|
|
71
|
+
const result = await api.exportXlsx({
|
|
72
|
+
from: '2026-10-01',
|
|
73
|
+
to: '2026-10-17',
|
|
74
|
+
where: { $and: [] },
|
|
75
|
+
})
|
|
76
|
+
expect(result.byteLength).toBe(4)
|
|
77
|
+
const [method, url] = requestRaw.mock.calls[0] ?? []
|
|
78
|
+
expect(method).toBe('GET')
|
|
79
|
+
expect(url).toMatch(/^\/credit-movements\/export\?/)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('exportXlsx refuses a client without requestRaw', async () => {
|
|
83
|
+
const withoutRaw: ApiClient = { ...client, requestRaw: undefined }
|
|
84
|
+
await expect(
|
|
85
|
+
new CreditMovementsApi(withoutRaw).exportXlsx({ from: 'a', to: 'b', where: { $and: [] } }),
|
|
86
|
+
).rejects.toThrow('requestRaw')
|
|
87
|
+
})
|
|
88
|
+
})
|
|
@@ -4,12 +4,19 @@ import type {
|
|
|
4
4
|
CreditMovement,
|
|
5
5
|
CreateCreditMovementPayload,
|
|
6
6
|
UpdateCreditMovementPayload,
|
|
7
|
+
CreditBalances,
|
|
8
|
+
CreditBalanceV2,
|
|
9
|
+
CreditBalancesV2Query,
|
|
10
|
+
CreditMovementServiceType,
|
|
11
|
+
CreditMovementsExportQuery,
|
|
12
|
+
CreditTotalServices,
|
|
13
|
+
CreditTotalServicesQuery,
|
|
7
14
|
} from './types'
|
|
8
15
|
|
|
9
16
|
/**
|
|
10
17
|
* Credit movements of the account (SMS and AI credits), only for reading: the writes are super
|
|
11
|
-
* admin routes (`/admin/credit-movements`).
|
|
12
|
-
*
|
|
18
|
+
* admin routes (`/admin/credit-movements`). Besides the list and the read by id, the backend serves the
|
|
19
|
+
* balances, the totals per connection and the xlsx export.
|
|
13
20
|
*/
|
|
14
21
|
export class CreditMovementsApi extends BaseCrudApi<
|
|
15
22
|
CreditMovement,
|
|
@@ -41,4 +48,68 @@ export class CreditMovementsApi extends BaseCrudApi<
|
|
|
41
48
|
override deleteById(id: string, headers?: Record<string, string>): Promise<CreditMovement> {
|
|
42
49
|
return super.deleteById(id, headers)
|
|
43
50
|
}
|
|
51
|
+
|
|
52
|
+
/** The balance of each service type of the account. */
|
|
53
|
+
balances(headers?: Record<string, string>): Promise<CreditBalances> {
|
|
54
|
+
return this.client.get<CreditBalances>(`${this.basePath}/balances`, headers)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The balance of one service type (a read, despite the POST). Without `serviceType` the route answers 500.
|
|
59
|
+
*/
|
|
60
|
+
balance(
|
|
61
|
+
serviceType: CreditMovementServiceType,
|
|
62
|
+
headers?: Record<string, string>,
|
|
63
|
+
): Promise<number> {
|
|
64
|
+
return this.client.post<number, { serviceType: CreditMovementServiceType }>(
|
|
65
|
+
`${this.basePath}/balance`,
|
|
66
|
+
{ serviceType },
|
|
67
|
+
headers,
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The balances of the period by service type and connection (`ins` without the trial credits). */
|
|
72
|
+
balancesV2(
|
|
73
|
+
query?: CreditBalancesV2Query,
|
|
74
|
+
headers?: Record<string, string>,
|
|
75
|
+
): Promise<Record<CreditMovementServiceType, CreditBalanceV2>> {
|
|
76
|
+
return this.client.get<Record<CreditMovementServiceType, CreditBalanceV2>>(
|
|
77
|
+
`${this.basePath}/v2/balances${this.toQueryString(query)}`,
|
|
78
|
+
headers,
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The total of credits of each connection in the period (five per page). Resolves `undefined` (no body)
|
|
84
|
+
* when `filters.from`, `filters.to`, `filters.serviceType` or `page` is missing.
|
|
85
|
+
*/
|
|
86
|
+
totalServices(
|
|
87
|
+
query: CreditTotalServicesQuery,
|
|
88
|
+
headers?: Record<string, string>,
|
|
89
|
+
): Promise<CreditTotalServices | undefined> {
|
|
90
|
+
return this.client.get<CreditTotalServices | undefined>(
|
|
91
|
+
`${this.basePath}/total-services${this.toQueryString(query)}`,
|
|
92
|
+
headers,
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Exports the movements of the period as an xlsx workbook (binary). Needs a client with `requestRaw`.
|
|
98
|
+
* Without `where.$and` the route answers 500.
|
|
99
|
+
*/
|
|
100
|
+
async exportXlsx(
|
|
101
|
+
query: CreditMovementsExportQuery,
|
|
102
|
+
headers?: Record<string, string>,
|
|
103
|
+
): Promise<ArrayBuffer> {
|
|
104
|
+
if (!this.client.requestRaw) {
|
|
105
|
+
throw new Error('exportXlsx needs a client with requestRaw (the answer is a binary xlsx)')
|
|
106
|
+
}
|
|
107
|
+
const response = await this.client.requestRaw(
|
|
108
|
+
'GET',
|
|
109
|
+
`${this.basePath}/export${this.toQueryString(query)}`,
|
|
110
|
+
undefined,
|
|
111
|
+
headers,
|
|
112
|
+
)
|
|
113
|
+
return response.arrayBuffer()
|
|
114
|
+
}
|
|
44
115
|
}
|
|
@@ -26,6 +26,7 @@ export type CreditMovementOrigin =
|
|
|
26
26
|
| 'additional'
|
|
27
27
|
| 'agent'
|
|
28
28
|
|
|
29
|
+
/** A movement as the transformer answers it (no `userId`; the relations are never served). */
|
|
29
30
|
export type CreditMovement = {
|
|
30
31
|
id: string
|
|
31
32
|
type: CreditMovementType
|
|
@@ -33,19 +34,90 @@ export type CreditMovement = {
|
|
|
33
34
|
serviceType: CreditMovementServiceType
|
|
34
35
|
origin: CreditMovementOrigin | null
|
|
35
36
|
accountId: string
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
/** `null` on the movements of the account (e.g. the trial SMS credits of a new account). */
|
|
38
|
+
serviceId: string | null
|
|
39
|
+
/** @deprecated Not answered: the transformer of the backend does not send it. */
|
|
40
|
+
userId?: string | null
|
|
38
41
|
campaignId: string | null
|
|
39
42
|
createdAt: string
|
|
40
43
|
updatedAt: string
|
|
41
44
|
// relationships
|
|
45
|
+
/** @deprecated Not served: the include is dropped by the backend. */
|
|
42
46
|
service?: Service
|
|
47
|
+
/** @deprecated Not served: the include is dropped by the backend. */
|
|
43
48
|
user?: User | null
|
|
49
|
+
/** @deprecated Not served: the include is dropped by the backend. */
|
|
44
50
|
campaign?: Campaign | null
|
|
45
51
|
}
|
|
46
52
|
|
|
53
|
+
/** None is served: the backend drops every include of this route. */
|
|
47
54
|
export type CreditMovementRelationships = 'service' | 'user' | 'campaign'
|
|
48
55
|
|
|
56
|
+
/** The balance of each service type (`balances`): the movements in minus the movements out. */
|
|
57
|
+
export type CreditBalances = Record<CreditMovementServiceType, number>
|
|
58
|
+
|
|
59
|
+
/** The totals of a service type in `balancesV2`. */
|
|
60
|
+
export type CreditBalanceTotals = {
|
|
61
|
+
ins: number
|
|
62
|
+
outs: number
|
|
63
|
+
balance: number
|
|
64
|
+
used: number
|
|
65
|
+
contractedCredits?: number
|
|
66
|
+
additionalCredits?: number
|
|
67
|
+
realConsumption?: number
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* A service type in `balancesV2`: `all` with the totals, `history` with the rows of the period, and one
|
|
72
|
+
* key per connection name with its totals. The `ins` count the credits of the plan (`plan.ai`) and the
|
|
73
|
+
* additional ones: the trial SMS credits are not counted (they show in `balances`).
|
|
74
|
+
*/
|
|
75
|
+
export type CreditBalanceV2 = {
|
|
76
|
+
all: CreditBalanceTotals
|
|
77
|
+
history?: Record<string, unknown>[]
|
|
78
|
+
[serviceName: string]: unknown
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type CreditBalancesV2Query = {
|
|
82
|
+
from?: string
|
|
83
|
+
to?: string
|
|
84
|
+
serviceId?: string
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Query of `totalServices`: without `filters.from`, `filters.to`, `filters.serviceType` and `page` the route
|
|
89
|
+
* answers 200 with no body.
|
|
90
|
+
*/
|
|
91
|
+
export type CreditTotalServicesQuery = {
|
|
92
|
+
filters: {
|
|
93
|
+
from: string
|
|
94
|
+
to: string
|
|
95
|
+
serviceType: CreditMovementServiceType
|
|
96
|
+
servicesIds?: string[]
|
|
97
|
+
}
|
|
98
|
+
/** From 1 (`0` answers 500 `Invalid param`). Five connections per page. */
|
|
99
|
+
page: number
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** The total of credits of each connection in the period, five per page (the counts come as text). */
|
|
103
|
+
export type CreditTotalServices = {
|
|
104
|
+
data: { id: string; name: string; total: string }[]
|
|
105
|
+
total: string
|
|
106
|
+
limit: number
|
|
107
|
+
skip: number
|
|
108
|
+
currentPage: string | number
|
|
109
|
+
lastPage: number
|
|
110
|
+
from: number
|
|
111
|
+
to: number
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Query of `exportXlsx`: the period and the conditions in `where.$and` (required by the backend). */
|
|
115
|
+
export type CreditMovementsExportQuery = {
|
|
116
|
+
from: string
|
|
117
|
+
to: string
|
|
118
|
+
where: { $and: Record<string, unknown>[] }
|
|
119
|
+
}
|
|
120
|
+
|
|
49
121
|
export type CreateCreditMovementPayload = {
|
|
50
122
|
type: CreditMovementType
|
|
51
123
|
serviceType: CreditMovementServiceType
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { KnowledgeBaseApi } from './KnowledgeBaseApi'
|
|
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
|
+
requestRaw: 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('KnowledgeBaseApi', () => {
|
|
23
|
+
let client: ReturnType<typeof createMockClient>
|
|
24
|
+
let api: KnowledgeBaseApi
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
client = createMockClient()
|
|
28
|
+
api = new KnowledgeBaseApi(client)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('saveWithItems posts the base with its items', async () => {
|
|
32
|
+
const body = { name: 'QA', items: [], deleteItemIds: [] }
|
|
33
|
+
await api.saveWithItems(body)
|
|
34
|
+
expect(client.post).toHaveBeenCalledWith('/knowledge-base/save-with-items', body, undefined)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('suggestByMessage posts the message to /knowledge-base/suggest-by-message', async () => {
|
|
38
|
+
await api.suggestByMessage({ messageId: 'm-1' })
|
|
39
|
+
expect(client.post).toHaveBeenCalledWith(
|
|
40
|
+
'/knowledge-base/suggest-by-message',
|
|
41
|
+
{ messageId: 'm-1' },
|
|
42
|
+
undefined,
|
|
43
|
+
)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('suggestByTicket posts the ticket to /knowledge-base/suggest-by-ticket', async () => {
|
|
47
|
+
await api.suggestByTicket({ ticketId: 't-1', outsideTheKnowledgeBase: true })
|
|
48
|
+
expect(client.post).toHaveBeenCalledWith(
|
|
49
|
+
'/knowledge-base/suggest-by-ticket',
|
|
50
|
+
{ ticketId: 't-1', outsideTheKnowledgeBase: true },
|
|
51
|
+
undefined,
|
|
52
|
+
)
|
|
53
|
+
})
|
|
54
|
+
})
|
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
import type { ApiClient } from '../../core/ApiClient'
|
|
2
2
|
import type { ListQuery } from '../../core/types'
|
|
3
3
|
import { BaseCrudApi } from '../../core/BaseCrudApi'
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
KnowledgeBase,
|
|
6
|
+
CreateKnowledgeBasePayload,
|
|
7
|
+
UpdateKnowledgeBasePayload,
|
|
8
|
+
KnowledgeBaseSuggestion,
|
|
9
|
+
SaveKnowledgeBaseWithItemsPayload,
|
|
10
|
+
SavedKnowledgeBase,
|
|
11
|
+
SuggestByMessagePayload,
|
|
12
|
+
SuggestByTicketPayload,
|
|
13
|
+
} from './types'
|
|
5
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Knowledge bases of the AI (copilot). The CRUD of a base without items only touches the database; an item
|
|
17
|
+
* VECTORIZES its content in the AI service, and the suggestions call the AI and DEBIT the copilot credits of
|
|
18
|
+
* the account.
|
|
19
|
+
*/
|
|
6
20
|
export class KnowledgeBaseApi extends BaseCrudApi<
|
|
7
21
|
KnowledgeBase,
|
|
8
22
|
CreateKnowledgeBasePayload,
|
|
@@ -22,4 +36,50 @@ export class KnowledgeBaseApi extends BaseCrudApi<
|
|
|
22
36
|
override deleteById(id: string, headers?: Record<string, string>): Promise<false> {
|
|
23
37
|
return super.deleteById(id, headers)
|
|
24
38
|
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Creates a base, or updates the one of `id`, with its items, in a transaction. Each item VECTORIZES its
|
|
42
|
+
* content in the AI service (with `items: []` only the database is touched). Resolves the stored base
|
|
43
|
+
* (raw). A body out of the schema answers 500 with the issues of the validation.
|
|
44
|
+
*/
|
|
45
|
+
saveWithItems(
|
|
46
|
+
body: SaveKnowledgeBaseWithItemsPayload,
|
|
47
|
+
headers?: Record<string, string>,
|
|
48
|
+
): Promise<SavedKnowledgeBase> {
|
|
49
|
+
return this.client.post<SavedKnowledgeBase, SaveKnowledgeBaseWithItemsPayload>(
|
|
50
|
+
`${this.basePath}/save-with-items`,
|
|
51
|
+
body,
|
|
52
|
+
headers,
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Suggests an answer to a message with the AI (copilot) and DEBITS the copilot credits of the account; an
|
|
58
|
+
* audio is transcribed first (charged too).
|
|
59
|
+
*/
|
|
60
|
+
suggestByMessage(
|
|
61
|
+
body: SuggestByMessagePayload,
|
|
62
|
+
headers?: Record<string, string>,
|
|
63
|
+
): Promise<KnowledgeBaseSuggestion> {
|
|
64
|
+
return this.client.post<KnowledgeBaseSuggestion, SuggestByMessagePayload>(
|
|
65
|
+
`${this.basePath}/suggest-by-message`,
|
|
66
|
+
body,
|
|
67
|
+
headers,
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Suggests an answer to a ticket with the AI (copilot) and DEBITS the copilot credits of the account; the
|
|
73
|
+
* audios of the ticket are transcribed first (charged too).
|
|
74
|
+
*/
|
|
75
|
+
suggestByTicket(
|
|
76
|
+
body: SuggestByTicketPayload,
|
|
77
|
+
headers?: Record<string, string>,
|
|
78
|
+
): Promise<KnowledgeBaseSuggestion> {
|
|
79
|
+
return this.client.post<KnowledgeBaseSuggestion, SuggestByTicketPayload>(
|
|
80
|
+
`${this.basePath}/suggest-by-ticket`,
|
|
81
|
+
body,
|
|
82
|
+
headers,
|
|
83
|
+
)
|
|
84
|
+
}
|
|
25
85
|
}
|